注册
|
登录
发帖
热搜
活动
交友
discuz
论坛
BBS
翼度工具
翼度网址导航
开发工具
Linux命令速查
网页设计配色表
在线制作icon
颜色代码选取器
翼度科技
»
论坛
›
编程开发
›
.net
›
查看内容
返回列表
发新帖
C# golang 开10000个无限循环的性能
一粒风
一粒风
当前离线
积分
15
5
主题
5
帖子
15
积分
新手上路
新手上路, 积分 15, 距离下一级还需 35 积分
新手上路, 积分 15, 距离下一级还需 35 积分
积分
15
发消息
显示全部楼层
知乎上有人提了个问题,可惜作者已把账号注销了。
复制一下他的问题,仅讨论技术用,侵删。
问题
作者:知乎用户fLP2gX
链接:
https://www.zhihu.com/question/634840187/answer/3328710757
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
最近遇见个需求,需要开2000个线程无限循环,每个循环有sleep(1),这个在其他语言很容易实现,在c#中就很难了,我试过task.delay(1)直接一秒钟10次gc。今天有空测试下多种语言的协程,都是开10000个协程无限循环,中间有个sleep(15ms), cpu使用率rust 40%,golang 3%,c# 16%, 都是release,把我搞不自信了。cpu是11代i5 ,rust的开销简直无法忍受。为了严谨测试了系统线程,cpu使用率43%
rust代码
static NUM: i64 = 0;
async fn fff() {
let t = tokio::time::Duration::from_millis(15);
loop {
tokio::time::sleep(t).await;
if NUM > 1000 {
println!("大于");
}
}
}
#[tokio::main]
async fn main() {
let mut i = 0;
while i < 10000 {
tokio::task::spawn(fff());
i = i + 1;
}
println!("over");
let mut s = String::new();
std::io::stdin().read_line(&mut s).unwrap();
}
复制代码
go代码
package main
import (
"fmt"
"time"
)
var AAA int
func fff() {
for {
time.Sleep(time.Millisecond * 15)
if AAA > 10000 {
fmt.Println("大于")
}
}
}
func main() {
for i := 0; i < 10000; i++ {
go fff()
}
fmt.Println("begin")
var s string
fmt.Scanln(&s)
}
复制代码
c#代码
internal class Program
{
static Int64 num = 0;
static async void fff()
{
while (true)
{
await Task.Delay(15);
if (num > 100000)
Console.WriteLine("大于");
}
}
static void Main()
{
for (int i = 0; i < 10000; i++)
fff();
Console.WriteLine("begin");
Console.ReadLine();
}
}
复制代码
我的测试
我使用Task.Delay测试,发现速度只有30多万次/秒,然后CPU占用达到30%。
然后我又上网了找了一个时间轮算法HashedWheelTimer,使用它的Delay,经过调参,速度可以达到50多万次/秒,达到了题主的要求,但CPU占用依然高达30%。我不知道是不是我找的这个HashedWheelTimer写的不好。
我的尝试
如下代码勉强达到了题主的要求,速度可以达到50多万次/秒,CPU占用8%,比go的3%要高一些,但比用Task.Delay要好很多了。但有个缺点,就是任务延迟可能会高达500毫秒。
int num = 0;
async void func(int i)
{
int n = 25; // 无延迟干活次数
int m = 1; // 干n次活,m次延迟干活
int t = 500; // 延迟干活时间,根据具体业务设置可以接受的延迟时间
long count = 0;
while (true)
{
if (count < n)
{
await Task.CompletedTask;
}
else if (count < n + m)
{
await Task.Delay(t); // 循环执行了若干次,休息一会,把机会让给其它循环,毕竟CPU就那么多
}
else
{
count = 0;
}
count++;
Interlocked.Increment(ref num); // 干活
}
}
for (int i = 0; i < 10000; i++)
{
func(i);
}
_ = Task.Factory.StartNew(() =>
{
Stopwatch sw = Stopwatch.StartNew();
while (true)
{
Thread.Sleep(5000);
double speed = num / sw.Elapsed.TotalSeconds;
Console.WriteLine($"10000个循环干活总速度={speed:#### ####.0} 次/秒");
}
}, TaskCreationOptions.LongRunning);
Console.WriteLine("begin");
Console.ReadLine();
复制代码
再次尝试
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Runtime.CompilerServices;
int num = 0;
MyTimer myTimer = new MyTimer(15, 17000);
async void func(int i)
{
while (true)
{
await myTimer.Delay();
// Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss.ffff} - {i}");
Interlocked.Increment(ref num); // 干活
}
}
for (int i = 0; i < 10000; i++)
{
func(i);
}
_ = Task.Factory.StartNew(() =>
{
Stopwatch sw = Stopwatch.StartNew();
while (true)
{
Thread.Sleep(5000);
double speed = num / sw.Elapsed.TotalSeconds;
Console.WriteLine($"10000个循环干活总速度={speed:#### ####.0} 次/秒");
}
}, TaskCreationOptions.LongRunning);
Console.WriteLine("开始");
Console.ReadLine();
myTimer.Dispose();
class MyTimer : IDisposable
{
private int _interval;
private Thread _thread;
private bool _threadRunning = false;
private ConcurrentQueue<MyAwaiter> _queue;
/// <summary>
/// Timer
/// </summary>
/// <param name="interval">时间间隔</param>
/// <param name="parallelCount">并行数量,建议小于或等于循环次数</param>
public MyTimer(int interval, int parallelCount)
{
_interval = interval;
_queue = new ConcurrentQueue<MyAwaiter>();
_threadRunning = true;
_thread = new Thread(() =>
{
while (_threadRunning)
{
for (int i = 0; i < parallelCount; i++)
{
if (_queue.TryDequeue(out MyAwaiter myAwaiter))
{
myAwaiter.Run();
}
}
Thread.Sleep(_interval);
}
});
_thread.Start();
}
public MyAwaiter Delay()
{
MyAwaiter awaiter = new MyAwaiter(this);
_queue.Enqueue(awaiter);
return awaiter;
}
public void Dispose()
{
_threadRunning = false;
}
}
class MyAwaiter : INotifyCompletion
{
private MyTimer _timer;
private Action _continuation;
private object _lock = new object();
public bool IsCompleted { get; private set; }
public MyAwaiter(MyTimer timer)
{
_timer = timer;
}
public void OnCompleted(Action continuation)
{
lock (_lock)
{
_continuation = continuation;
if (IsCompleted)
{
_continuation.Invoke();
}
}
}
public void Run()
{
lock (_lock)
{
IsCompleted = true;
_continuation?.Invoke();
}
}
public MyAwaiter GetAwaiter()
{
return this;
}
public object GetResult()
{
return null;
}
}
复制代码
时间轮算法有点难写,我还没有掌握,换了一种写法,达到了题主的要求,速度可以达到50多万次/秒,CPU占用3%。但有缺点,MyTimer用完需要Dispose,有个并行度参数parallelCount需要根据测试代码中for循环次数设置,设置为for循环次数的1.7倍,这个参数很讨厌,再一个就是Delay时间设置了15毫秒,但是不精确,实际任务延迟可能会超出15毫秒,或者小于15毫秒,当然这里假设计时器是精确的,实际上计时器误差可能到达10毫秒,这里认为它是精确无误差的,在这个前提下,任务执行间隔不精确,但比上次尝试,最大延迟500毫秒应该要好很多。
本人水平有限,写的匆忙,但我感觉这个问题还是很重要的。问题简单来说就是大量Task.Delay会导致性能问题,有没有更高效的Delay实现?
注意,和时间轮算法所面对的需求可能并不一样,这里是为了实现高性能的Delay,而不是为了实现简化版的Quartz。
自己写复杂的东西很容易写出事
有一段代码我是这么写的:
public void OnCompleted(Action continuation)
{
_continuation = continuation;
}
public void Run()
{
IsCompleted = true;
_continuation?.Invoke();
}
复制代码
然后在某些场景下测试就出事了,我默默改成了:
public void OnCompleted(Action continuation)
{
_continuation = continuation;
if (IsCompleted)
{
Interlocked.Exchange(ref _continuation, null)?.Invoke();
}
}
public void Run()
{
IsCompleted = true;
Interlocked.Exchange(ref _continuation, null)?.Invoke();
}
复制代码
但是这个代码,在特定场景下依然不靠谱,测试代码部分改动如下:
int num = 0;
MyTimer myTimer = new MyTimer(15, 2000);
ThreadPool.SetMaxThreads(2300, 2300);
ThreadPool.SetMinThreads(2200, 2200);
async void func(int i)
{
while (true)
{
await myTimer.Delay();
// Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss.ffff} - {i}");
await Task.Run(() =>
{
Thread.Sleep(100);
Interlocked.Increment(ref num); // 干活
});
}
}
for (int i = 0; i < 2000; i++)
{
func(i);
}
复制代码
然后又出事了,await myTimer.Delay()换成Task.Delay(15)就正常。但是我就不知道原因了。在测试过程中,有一些循环跑着跑着就停止了,我抄的大牛写的代码,按说应该不存在线程安全问题了吧,但还是不行。
Task.Delay靠谱是靠谱,但是CPU占用高,因为每一个Delay都会创建一个Timer,大量Delay会占用较多CPU。
再改一下:
public void OnCompleted(Action continuation)
{
lock (_lock)
{
_continuation = continuation;
if (IsCompleted)
{
_continuation.Invoke();
}
}
}
public void Run()
{
lock (_lock)
{
IsCompleted = true;
_continuation?.Invoke();
}
}
复制代码
bug没有重现了。
再思考
感觉异步的使用还是要结合具体问题具体分析。异步主要用于IO密集型任务,用来提高吞吐量。但总有人拿他来测试CPU密集型任务,然后问出很diao钻的问题。
来源:
https://www.cnblogs.com/s0611163/p/17970369
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!
上一篇:
C# golang 开10000个无限循环的性能
下一篇:
C# golang 开10000个无限循环的性能
发表于 2024-1-20 10:45:02
举报
回复
使用道具
分享
返回列表
发新帖
本版积分规则
高级模式
B
Color
Image
Link
Quote
Code
Smilies
您需要登录后才可以回帖
登录
|
立即注册
快速回复
快速回复
返回顶部
返回顶部
返回列表
返回列表