C# golang 开10000个无限循环的性能
知乎上有人提了個問題,可惜作者已把賬號注銷了。
復制一下他的問題,僅討論技術用,侵刪。
問題
作者:知乎用戶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 myTime = new MyTimer(15, 17000);
async void func(int i)
{
while (true)
{
await myTime.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();
myTime.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,如果是1萬個plc呢?如果要求Delay(15),就不能像我這樣寫了。但是,你看看,這樣寫有多么簡單?!本來一個多線程并行問題,寫起來很復雜,很容易寫出bug,如果能像同步代碼這樣寫,寫出來性能不亞于多線程并行,邏輯簡單,不容易出bug。
總結
以上是生活随笔為你收集整理的C# golang 开10000个无限循环的性能的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 塑料也能造芯片?
- 下一篇: C#:表白程序(满屏玫瑰花)-让屏幕开满