最近在開發一些很講求速度的程式,對於"程式夠不夠快"這件事有了全新的體認!

以往寫Web時,User多半很能忍受看網頁本來就需要等待這件事實,所以只要不太離諎,3,5秒的Delay多半還可被接受。而最近在處理的案子是那種速度絕對至上的典型,慢了1ms程式就變成廢柴,因此每個環節都要求快,變成不能用傳統正規的做法解決,到處都在走偏鋒,才能達成目標。

CodeProject上有篇很棒的研究--Timer Surprises,看過之後才發現Timer.Interval設成15ms以下根本就是自欺欺人,而文中所附的程式是個很好的實證,可以測試你的電腦如何實現Timter.Interval 1ms, Thread.Sleep 1ms,結果會讓你很吃驚。

在我的兩台號稱旗艦的主機上執行的結果,Thread.Sleep可以達到極接近的精確度,滿心歡喜地想應用在試測環境上,沒想到... 天哪,結果竟不相同。於是我簡化了程式碼,寫了一個Console Application-TestThreadSleep,蒐集手邊幾台機器的執行結果,發現一個很有趣的事實! 如同Timer Surprises文中所說的,作者沒找出決定Thread.Sleep實際停止時間的因素,我也暫無定論(但有些推測)。

以下是我的測試結果:

XPC SD27P2 Core 2 Duo E6400 2.16G Win 2003

Thread.Sleep(25) Test:
25.374 ms, 25.261 ms, 25.337 ms, 25.362 ms, 25.241 ms, Avg = 25.315 ms
Thread.Sleep(15) Test:
15.484 ms, 15.581 ms, 15.580 ms, 15.563 ms, 15.573 ms, Avg = 15.556 ms
Thread.Sleep(10) Test:
10.618 ms, 10.707 ms, 10.689 ms, 10.708 ms, 10.688 ms, Avg = 10.682 ms
Thread.Sleep(5) Test:
5.216 ms, 5.686 ms, 5.812 ms, 5.822 ms, 5.817 ms, Avg = 5.671 ms
Thread.Sleep(1) Test:
1.362 ms, 1.906 ms, 1.915 ms, 1.917 ms, 1.916 ms, Avg = 1.803 ms

IBM ThinkPad X21 PIII-700M Hz XP

Thread.Sleep(25) Test:
24.025 ms, 29.655 ms, 29.990 ms, 30.028 ms, 29.986 ms, Avg = 28.737 ms
Thread.Sleep(15) Test:
19.893 ms, 20.020 ms, 19.994 ms, 19.943 ms, 19.971 ms, Avg = 19.964 ms
Thread.Sleep(10) Test:
9.930 ms, 9.999 ms, 9.983 ms, 10.003 ms, 9.977 ms, Avg = 9.978 ms
Thread.Sleep(5) Test:
9.929 ms, 10.009 ms, 10.112 ms, 9.998 ms, 9.973 ms, Avg = 10.004 ms
Thread.Sleep(1) Test:
9.985 ms, 9.797 ms, 9.990 ms, 10.004 ms, 9.981 ms, Avg = 9.951 ms

XPC SB91P P4 3.2G Vista

Thread.Sleep(25) Test:
24.541 ms, 25.147 ms, 25.260 ms, 25.282 ms, 25.236 ms, Avg = 25.093 ms
Thread.Sleep(15) Test:
15.472 ms, 15.535 ms, 15.531 ms, 15.523 ms, 15.523 ms, Avg = 15.517 ms
Thread.Sleep(10) Test:
10.606 ms, 10.647 ms, 10.607 ms, 10.649 ms, 10.616 ms, Avg = 10.625 ms
Thread.Sleep(5) Test:
5.700 ms, 5.743 ms, 5.819 ms, 5.693 ms, 5.780 ms, Avg = 5.747 ms
Thread.Sleep(1) Test:
1.842 ms, 1.910 ms, 1.884 ms, 1.860 ms, 1.894 ms, Avg = 1.878 ms

HP Proliant DL360 G3 P4 3.4G Win2003

Thread.Sleep(25) Test:
29.770 ms, 30.159 ms, 30.941 ms, 31.194 ms, 31.180 ms, Avg = 30.649 ms
Thread.Sleep(15) Test:
15.682 ms, 15.323 ms, 15.551 ms, 15.604 ms, 15.561 ms, Avg = 15.544 ms
Thread.Sleep(10) Test:
15.458 ms, 15.576 ms, 15.819 ms, 15.373 ms, 15.456 ms, Avg = 15.536 ms
Thread.Sleep(5) Test:
15.454 ms, 15.599 ms, 15.605 ms, 15.468 ms, 15.809 ms, Avg = 15.587 ms
Thread.Sleep(1) Test:
15.269 ms, 15.562 ms, 15.513 ms, 15.559 ms, 15.555 ms, Avg = 15.491 ms

HP Proliant DL360 P-III 1.26G Win 2000

Thread.Sleep(25) Test:
23.968 ms, 31.266 ms, 30.447 ms, 31.165 ms, 31.041 ms, Avg = 29.577 ms
Thread.Sleep(15) Test:
15.682 ms, 15.291 ms, 15.557 ms, 15.521 ms, 15.550 ms, Avg = 15.520 ms
Thread.Sleep(10) Test:
15.486 ms, 15.463 ms, 15.682 ms, 15.418 ms, 15.517 ms, Avg = 15.513 ms
Thread.Sleep(5) Test:
15.472 ms, 15.552 ms, 15.535 ms, 15.569 ms, 15.709 ms, Avg = 15.568 ms
Thread.Sleep(1) Test:
15.290 ms, 15.539 ms, 15.559 ms, 15.551 ms, 15.554 ms, Avg = 15.499 ms

由這個結果來看,Thead.Sleep下去會不會準時起床似乎非關CPU快慢,倒是跟主機板比較有關(例如: 兩台XPC一致,兩台ProLiant Server一致),兩台HP主機,似乎還是卡在無法切出小於15ms的精確度,小黑X21則是努力的要精準,無奈CPU力不從心~~~

只是由這個結論來看,想利用Thread.Sleep更精準控制時間的構想會因機器不同而破功,看來是不可行的,殘念!


Comments

# by 小熊子

沒想到 Thread.Sleep 有這麼多學問,不知道 C++ 會不會這樣子哦~ 以前在測試效能時,managed 毫秒比起 unmanaged 的精準度差,不過忘了那邊看到的,說不定是一個另外的方向

# by steve

在Timer Surprises這篇文章下面的Comment裡有這麼一段話 Thread.Sleep() is doing exactly what's it's meant for: relinquishing the rest of your thread's time slice to the scheduler, and asking it not to be run again for the number of milliseconds you pass. The system is never guaranteed to run your thread again after the time you requested expires... 看來起床時間很可能跟系統層面有關,或許不一定跟硬體有關? 另外,在MSDN Library上 http://msdn2.microsoft.com/en-us/library/ms686298.aspx 也提到Win32API的Sleep 第一句話就開宗明義的說 Suspends the execution of the current thread for AT LEAST the specified interval 後面也有說明 This function causes a thread to relinquish the remainder of its time slice and become unrunnable for an interval based on the value of dwMilliseconds. The system clock "ticks" at a constant rate. If dwMilliseconds is less than the resolution of the system clock, the thread may sleep for less than the specified length of time. If dwMilliseconds is greater than one tick but less than two, the wait can be anywhere between one and two ticks 所以睡覺時間不準,似乎是件合理的事......

# by 璉璉

剛剛從我的 live spaces 那邊看到推薦來源是這邊,就過來喵了一下... 關於這篇,我有幾篇相關的文章: VB6/VBNET CPU 資源釋出:http://tlcheng.spaces.live.com/blog/cns!145419920BFD55A7!1640.entry MyWait 函數:http://tlcheng.twbbs.org/TLCheng/Net/NetList.aspx?Action=Function&Module=31&Function=220 Sleep 每次睡,基本上在 WinNT 是 10ms,Win9x 是 54ms,這個在MSDN有提,可以直接搜尋 10 AND 54。 但部份硬體可以低於這個值,讓系統可以睡的更精確,是否能睡的更精確,可以用 API QueryPerformanceFrequency 看看頻率是否與本機 CPU 頻率接近。 我曾經有一台電腦 P4 3G 的電腦,那時用的是華碩主機板,用上面 API 測出來的 QueryPerformanceFrequency 約是 2.997 G,所以那時做上面那個 MyWait 函數時,可以精確到秒以下 5 位,也就是 0.01 ms,而誤差小於 5% ,也就是誤差小於 0.0005 ms (測試範例詳見上面網址說明) ,後來我不小心把主機板燒了,換了一塊技嘉主機板,南北橋雖然都相同,但是卻沒有那麼高的解析度,用 QueryPerformanceFrequency 跑就只有 1193180 (可用 Google 查,有的人乾脆當常數,關鍵字 QueryPerformanceFrequency 1193180) ,我現在這台 C2D E6400 的 CPU 用華碩 P5B 跑,QueryPerformanceFrequency ,得到的值是 2135090000 ,跟系統、內容的 CPU 時脈 2.14GHz 差不多,而跑上面 MyWait 得到: ? MyWait(0.001, False), MyWait(0.001, True) , MyWait(0.001, True, 1) 1.00014519294269E-03 1.00118683521538E-03 1.70647232669349E-03 第二、三個會呼叫 Sleep 睡 0, 1 ms,可以看得出來睡覺不會睡到 10 ms。 所以我覺得是硬體的問題,而硬體是否允許 Sleep 睡覺小於 10 ms ,可以比對 QueryPerformanceFrequency 回傳值是否大於 1193180 來判斷。

# by 璉璉

補充: 所以說 MSDN 在很早就有揭露這個訊息,並不需要特別去測試,在一般電腦上時間最小解析度是 10ms/ 54ms (WinNT/Win9x),但是若在硬體有更精確的時脈支援下,就可以達到 1ms,所以 Sleep 的精確度是 目標值+- 1ms (+- 1ms 是解析度限制造成誤差) ,若需要使用更高解析度的時間處理,就要改用 QueryPerformanceFrequency / QueryPerformanceCounter 。 註:VBNET 版我也有類似函數,不過懶的整理,所以沒上線。

# by 璉璉

再補充: 54 ms 是以前 386/486 主機板每秒震盪 18.4 Hz 算出來的: 1 / 18.4 = 0.0543478260 = 0.054 s = 54 ms 所以以前在 Win3.x 的年代最小也是 54 ms ,但是那時候的 WinNT 3.5/3.51 已經支援到 10 ms,以前要高解析度的時脈要用 WinNT 來跑。 註:WinNT 是 MSDN 定義的縮寫,表示 NT3.x/4/Win2k(5.0)/WinXP(5.1)/Win2003(5.2)/Vista(6.0)

# by Jeffrey

璉璉大大,幸會!! 會冒出這個需求是因為手上有個案子要做Polling,頻率愈高愈好,用Timer只能壓到15ms, 所以才把腦筋動到Sleep上。其實用Loop檢查QueryPerformanceCounter應該可以做到完全精準,只是Thread.Sleep時完全不耗CPU的特性無可取代。 昨天發現了一個新絕技timeBeginPeriod,可以讓Thread.Sleep精準度升高,可以參考今天Post的文章。

# by 璉璉

剛剛看了新增的那篇,以前大概有看過類似的文章,不過討論的對象是: Kernel32: GetTickCount winmm: timeGetTime 忘了是不是 MSDN Magazine,大概是說核心的函數庫以執行快速為優先,所以解析度不高,而多媒體的函數庫則以精準為目標,效能比較差,但是比較準。 當時我是 Pentium 233 MMX 的 CPU ,分別測試時間相關函數執行 100,000 次所需時間 (秒): QueryPerformanceCounter API 0.73167 GetTickCount API 0.13600 timeGetTime API 4.08500 Timer VB 12.96094 發布在:http://tlcheng.twbbs.org/TLCheng/Basic/vbtime.htm#通論 執行效能比較那段。 綜合評估後,我才選用:QueryPerformanceCounter 現在用 winmm 的函式庫去改時間解析度,不知道會不會嚴重影響效能... 不過我的硬體比較好,不用改 timeBeginPeriod 就可以跑到 1ms ,我是覺得這樣比較好啦,特定需求下,指定硬體要老闆買是比較好的。 先前有碰過雷達的案子,一秒有 4 GB 的資料塞進來... 這種只能靠高檔硬體來處理,現階段所有 PC 全死~ (只是先儲存,事後才抽取資料進行分析計算)

# by chronos

中斷解析度大多不是硬體問題, 大部分是系統上的設定限制, 使用函數 timeBeginPeriod(1); //設定中斷時解析度 會提高系負擔 Sleep 就是1ms了

# by houzin

Stopwatch sp = new Stopwatch(); public virtual void Sleep(int mvSleepMs) { sp.Restart(); while(true) { SpinWait.SpinUntil(() => { if (mvSleepMs <= 50 ) { if (sp.ElapsedTicks >= 100) { return true; } else { return false; } } return false; }, mvSleepMs); if(sp.ElapsedMilliseconds >= mvSleepMs ) { return; } } }

# by 小熊子

https://dotblogs.com.tw/yc421206/2013/04/25/102300 [C#.NET][Thread] 善用 SpinWait 處理 執行緒空轉 以利提昇效能 15:26 當我們在處理一個執行緒時,若需要同步等待時,以往可能會常用 Thread.Sleep,但 Thread.Sleep 會消耗 CPU 的時間配置,所以我們可以使用 Thread.SpinWait 方法 、SpinWait 結構 在 .NET4.0 以前,可以使用 Thread.SpinWait 方法 在 .NET4.0 以後,可以使用 SpinWait 結構 參考: https://docs.microsoft.com/zh-tw/dotnet/api/system.threading.spinwait.spinuntil?view=netcore-3.1 若在非同步的方法中要做 Thread.Sleep是非常危險,建議使用上述方法放開 若是 Thread.Sleep(0) 可參考await Task.Yield() 比較接近用法,供參考

Post a comment