注册
|
登录
发帖
热搜
活动
交友
discuz
论坛
BBS
翼度工具
翼度网址导航
开发工具
Linux命令速查
网页设计配色表
在线制作icon
颜色代码选取器
翼度科技
»
论坛
›
编程开发
›
.net
›
查看内容
返回列表
发新帖
C# golang 开10000个无限循环的性能
马玉玲
马玉玲
当前离线
积分
30
10
主题
10
帖子
30
积分
新手上路
新手上路, 积分 30, 距离下一级还需 20 积分
新手上路, 积分 30, 距离下一级还需 20 积分
积分
30
发消息
显示全部楼层
知乎上有人提了个问题,可惜作者已把账号注销了。
复制一下他的问题,仅讨论技术用,侵删。
问题
作者:知乎用户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;
public bool IsCompleted { get; private set; }
public MyAwaiter(MyTimer timer)
{
_timer = timer;
}
public void OnCompleted(Action continuation)
{
_continuation = continuation;
}
public void Run()
{
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实现?
这个问题有什么实际价值?看我另一个回答:
求助多线程读取大量PLC问题?
我给的回答:
for (int i = 0; i < 500; i++)
{
ReadPLC(i);
}
async void ReadPLC(int plcIndex)
{
while (true)
{
// todo: 读取PLC
Console.WriteLine($"读取PLC {plcIndex}");
await Task.Delay(200);
}
}
复制代码
还好它这只要求500个plc,间隔200毫秒也比较长,用Task.Delay完全可以,如果是1万个plc呢?如果要求Delay(15),就不能像我这样写了。但是,你看看,这样写有多么简单?!本来一个多线程并行问题,写起来很复杂,很容易写出bug,如果能像同步代码这样写,写出来性能不亚于多线程并行,逻辑简单,不容易出bug。
注意,和时间轮算法所面对的需求可能并不一样,这里是为了实现高性能的Delay,而不是为了实现简化版的Quartz。
来源:
https://www.cnblogs.com/s0611163/Undeclared/17970369
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!
上一篇:
C# golang 开10000个无限循环的性能
下一篇:
在Winform系统开发中,对表格列表中的内容进行分组展示
发表于 2024-1-17 20:55:01
举报
回复
使用道具
分享
返回列表
发新帖
本版积分规则
高级模式
B
Color
Image
Link
Quote
Code
Smilies
您需要登录后才可以回帖
登录
|
立即注册
快速回复
快速回复
返回顶部
返回顶部
返回列表
返回列表