匿名方法 vs 具名方法 的小小效能實驗
2 |
在過去,我習慣將要交給ThreadPool執行的程式邏輯另外寫成void NamedMethod(object arg) { … }裡,再配合ThreadPool.QueueUserWorkItem(new WaitCallback(NamedMethod), arg);。
近來讀到幾篇文章,發現高手們都很順手地用了Lambda演算式,習慣寫成ThreadPool.QueueUserWorkItem(arg => { … }, arg),將邏輯直接包在匿名方法中。不但程式碼變得更簡潔,二來程式邏輯出現位置等同執行時機,程式碼更加直覺化,可讀性更佳。
在更早一篇談Closure的文章曾提到,匿名方法的背後,Compiler會偷偷將其轉為對應的具名方法,並改寫部分程式(參考),理論上與具名方法做法相近,效能應不會差太多,但我還是挺想透過實驗驗證一下。另一方面,我想也藉此一併展示傳統QueueUserWorkItem寫法與Lambda極簡風的比較!
using System;
using System.Diagnostics;
using System.Threading;
namespace MultiCore
{
class TestAnonymousMethod
{
static int JOB_COUNT = 0;
//使用ManualResetEvent同步所有工作的完成時機
static ManualResetEvent syncEvent =
new ManualResetEvent(false);
static void Main(string[] args)
{
//做1000萬次
int TIMES = 1000 * 10000;
Stopwatch sw = new Stopwatch();
//反覆做五次以求驗證結果一致性
for (int round = 0; round < 5; round++)
{
sw.Reset();
//先填入待辦工作項目
JOB_COUNT = TIMES;
sw.Start();
for (int i = 0; i < TIMES; i++)
//排入1000萬件工作,傳統寫法
ThreadPool.QueueUserWorkItem(
new WaitCallback(NamedMethod), i);
//等待待辦工作件數為零,同步事件被觸發
syncEvent.WaitOne();
sw.Stop();
Console.WriteLine("Named Method = {0:N0}ms",
sw.ElapsedMilliseconds);
JOB_COUNT = TIMES;
syncEvent.Reset();
sw.Reset();
sw.Start();
for (int i = 0; i < TIMES; i++)
//使用Lambda演算式將計算邏輯放在匿名方法中
ThreadPool.QueueUserWorkItem(o =>
{
//隨便找點事就,就算Log10吧!
double d = Math.Log10(Convert.ToDouble(o));
//待辦工作項目減1
Interlocked.Decrement(ref JOB_COUNT);
//若沒有待辦事項,表示全部工作完成,觸發同步
if (JOB_COUNT == 0) syncEvent.Set();
}, i);
syncEvent.WaitOne();
sw.Stop();
Console.WriteLine("Anonymous Method = {0:N0}ms",
sw.ElapsedMilliseconds);
}
Console.Read();
}
static void NamedMethod(object arg)
{
double d = Math.Log10(Convert.ToDouble(arg));
Interlocked.Decrement(ref JOB_COUNT);
if (JOB_COUNT == 0) syncEvent.Set();
}
}
}
為了謹慎起見,我加入迴圈一口氣跑五次測試,試圖用多組數據來確保結果一致性。結論有點意思,在五次測試數據中,Lambda的寫法速度通通比傳統寫法還來得快。(【2009-01-02更新】若將NamedMethod方式迴圈裡的new WaitCallback移至迴圈外,則二者速度相當)
Named Method = 11,650ms
Anonymous Method = 9,151ms
Named Method = 9,724ms
Anonymous Method = 8,993ms
Named Method = 10,242ms
Anonymous Method = 9,079ms
Named Method = 11,611ms
Anonymous Method = 8,955ms
Named Method = 9,583ms
Anonymous Method = 9,007ms
透過這個實驗,我的小小心得是: 並未發現Lambda Expression有減損效能的事證,未來可大膽安心服用。
Comments
# by laneser
我的小小疑問是, 照理說, 理論上與具名方法做法相近, 或者說, 應該是一模一樣, 所耗費的事情 (產生具名方法的 code) 又在 Compiler time 做掉了, 速度應該也是一樣才對, 可是從數據來看卻有比較快? 該不會是 new WaitCallback() 的速度小於 new anonymous callback instance ? 我怎麼看大概只有這個地方會引發比較 lambda 比較快問題... 有空或許可以研究看看.
# by Jeffrey
to laneser, 我認為new WailCallback是會有影響的,而且可透過改寫 WaitCallback wb = new WaitCallback(NamedMethod); for (int i = 0; i < TIMES; i++) ThreadPool.QueueUserWorkItem(wb, i); 的方式改善,經實測二者的差距的確會變小,甚至互有消長。不過因文中用的是我習慣的傳統寫法,這點就沒有Tune。