在進入主題前,先來爆個料:

踢爆黑心程式碼,瞎忙半天幫倒忙!!

昨天我貼了一篇關於匿名方法與具名方法效能比較的文章,不知有沒有人發現到,其實裡面藏了一個天大的祕密!! Lambda寫法無損效能的結論是對的,但是,在這個範例裡用ThreadPool處理卻錯得離譜!

不信? 那我們先保留第二段的ThreadPool + Lambda寫法,但將第一段改成

for (int i = 0; i < TIMES; i++)
    NamedMethod(i);

換句話說,第一段程式不再管什麼鬼ThreadPool,直接用迴圈把1000萬次做完。執行的結果可能會讓你大吃一驚:

Named Method = 154ms
Anonymous Method = 1,117ms
Named Method = 141ms
Anonymous Method = 946ms
Named Method = 142ms
Anonymous Method = 970ms
Named Method = 146ms
Anonymous Method = 1,027ms
Named Method = 141ms
Anonymous Method = 1,049ms

啥? 不用ThreadPool跑,1000萬次的Log10計算只需要150ms(其實還可以省掉NamedMethod中的Interlocked.Decrement及ManualResetEvent,還可以更快),是用ThreadPool執行時間的1/7!!! 用了ThreadPool反而慢了七倍,那麼整個範例是寫心酸的? 於是我們學到第一課:

不是任何運算用ThreadPool執行都會加速!!

多執行緒程式不是很威嗎? 我測試的機器是Dual Core E6400,兩顆CPU分工合作不是應該要更快? 真相是---Multi-Threading的機制並非免費午餐,在運作時也要相對付出代價。ThreadPool的基本原理是將要執行的工作放進一個Queue裡,然後啟動多條Thread,每條Thread會去檢查Queue裡還有沒有工作要做,若有就取出來。由於要管控每件工作只交給其中一個Thread去處理,所以得動用lock機制;也就是同一時間只能開放一條Thread去檢查Queue並取出工作,可以想像成全部的Thread排成一列輪流存取。另外,在程式裡透過JOB_COUNT掌握工作何時全部做完,在遞減JOB_COUNT也要等同要啟動lock機制(Interlocked.Decrement),任何時間下只允許一個Thread去修改它的值。另外還有一點,CPU在切換不同Thread執行時,也要保留與切換Thread相關的暫存器、快取、堆疊等環境,稱作Context Switch,也會損耗一些運算資源。

用個例子來比喻,我們找了50位電腦組裝達人齊聚一堂,在教室最前方放了一個籃子,要求大家排隊(Queue)到籃子裡取出要組裝的套件包(WorkItem),組裝完成後再排隊(Interlocked.Decrement)到黑板畫正字統計件數。

50個高手分工合作一定比一個人單打獨鬥快嗎? 不一定!! 就要看組裝的套件是什麼。

如果要組裝的套件是"把筆蓋套到原子筆上",那麼動用一整個教室50個人排隊加寫黑板就變成在莊孝維(或是或是安排這個遊戲的人會被50個人海扁);但若是要處理的套件是"將CPU、MB、RAM、HD、Power、Case組成一台PC",則我們可以肯定這種作業方式會比一個人組裝快了近50倍。

好,那我們改一下程式為ThreadPool雪恥,把原本的Log10

double d = Math.Log10(Convert.ToDouble(o));

改成產生1000次亂數

Random rnd = new Random();
for (int k = 0; k < 1000; k++)
    rnd.Next();

JOB_COUNT次數則調成10萬次,結果如下:

Named Method = 2,276ms
Anonymous Method = 1,406ms
Named Method = 2,328ms
Anonymous Method = 1,477ms
Named Method = 2,240ms
Anonymous Method = 1,262ms
Named Method = 2,244ms
Anonymous Method = 1,291ms
Named Method = 2,231ms
Anonymous Method = 1,324ms

這回就換成ThreadPool佔上風了,由以上的案例裡我們得到一個心得--ThreadPool機制是有代價的,因此當工作本身並不複雜時,機制的成本便會壓過多工產生的效益。

至於要怎麼充分擠出Multi-Core CPU的運算能力,還有蠻多有趣的議題,下回再聊。


Comments

# by horngsh

好文, 贊!

# by Sam

寫得好! 我最近也遇到這個問題^^

# by faust

超棒的~ 寫得很清楚^^ 雖然我才剛開始接觸C#,但已碰到需要平行處理的概念 謝謝

# by Jerry Chen

.NET Framework 4的Thread Pool已經改用lock-free的方式存取Queue中的work item了。 " In the .NET Framework 4, this queue has been improved to use a lock-free algorithm that resembles the ConcurrentQueue(T) class. By using this lock-free implementation, the ThreadPool spends less time when it queues and de-queues work items" http://msdn.microsoft.com/en-us/library/dd997402(v=vs.110).aspx

# by Jeffrey

to Jerry Chen, 謝謝補充。

Post a comment