2012-06-13 43 views
15

我剛剛構建了動態方法 - 請參閱下文(感謝SO用戶)。看來,Func創建爲IL注入比lambda慢2倍的動態方法。爲什麼lambda比IL注入動態方法更快?

任何人都知道爲什麼呢?

(編輯:這是在VS2010建成發佈的x64請從控制檯無法從Visual Studio F5內運行它。)

class Program 
{ 
    static void Main(string[] args) 
    { 
     var mul1 = IL_EmbedConst(5); 
     var res = mul1(4); 

     Console.WriteLine(res); 

     var mul2 = EmbedConstFunc(5); 
     res = mul2(4); 

     Console.WriteLine(res); 

     double d, acc = 0; 

     Stopwatch sw = new Stopwatch(); 

     for (int k = 0; k < 10; k++) 
     { 
      long time1; 

      sw.Restart(); 

      for (int i = 0; i < 10000000; i++) 
      { 
       d = mul2(i); 
       acc += d; 
      } 

      sw.Stop(); 

      time1 = sw.ElapsedMilliseconds; 

      sw.Restart(); 

      for (int i = 0; i < 10000000; i++) 
      { 
       d = mul1(i); 
       acc += d; 
      } 

      sw.Stop(); 

      Console.WriteLine("{0,6} {1,6}", time1, sw.ElapsedMilliseconds); 
     } 

     Console.WriteLine("\n{0}...\n", acc); 
     Console.ReadLine(); 
    } 

    static Func<int, int> IL_EmbedConst(int b) 
    { 
     var method = new DynamicMethod("EmbedConst", typeof(int), new[] { typeof(int) }); 

     var il = method.GetILGenerator(); 

     il.Emit(OpCodes.Ldarg_0); 
     il.Emit(OpCodes.Ldc_I4, b); 
     il.Emit(OpCodes.Mul); 
     il.Emit(OpCodes.Ret); 

     return (Func<int, int>)method.CreateDelegate(typeof(Func<int, int>)); 
    } 

    static Func<int, int> EmbedConstFunc(int b) 
    { 
     return a => a * b; 
    } 
} 

這裏是輸出(用於I7 920)

20 
20 

25  51 
25  51 
24  51 
24  51 
24  51 
25  51 
25  51 
25  51 
24  51 
24  51 

4.9999995E+15... 

============================================== ==============================

EDIT EDIT編輯編輯

這裏是證明dhtorpe是正確的 - 更復雜的lambda將失去其優勢。 代碼以證明它(這表明,LAMBDA具有恰好與IL注射相同性能):

class Program 
{ 
    static void Main(string[] args) 
    { 
     var mul1 = IL_EmbedConst(5); 
     double res = mul1(4,6); 

     Console.WriteLine(res); 

     var mul2 = EmbedConstFunc(5); 
     res = mul2(4,6); 

     Console.WriteLine(res); 

     double d, acc = 0; 

     Stopwatch sw = new Stopwatch(); 

     for (int k = 0; k < 10; k++) 
     { 
      long time1; 

      sw.Restart(); 

      for (int i = 0; i < 10000000; i++) 
      { 
       d = mul2(i, i+1); 
       acc += d; 
      } 

      sw.Stop(); 

      time1 = sw.ElapsedMilliseconds; 

      sw.Restart(); 

      for (int i = 0; i < 10000000; i++) 
      { 
       d = mul1(i, i + 1); 
       acc += d; 
      } 

      sw.Stop(); 

      Console.WriteLine("{0,6} {1,6}", time1, sw.ElapsedMilliseconds); 
     } 

     Console.WriteLine("\n{0}...\n", acc); 
     Console.ReadLine(); 
    } 

    static Func<int, int, double> IL_EmbedConst(int b) 
    { 
     var method = new DynamicMethod("EmbedConstIL", typeof(double), new[] { typeof(int), typeof(int) }); 

     var log = typeof(Math).GetMethod("Log", new Type[] { typeof(double) }); 

     var il = method.GetILGenerator(); 

     il.Emit(OpCodes.Ldarg_0); 
     il.Emit(OpCodes.Ldc_I4, b); 
     il.Emit(OpCodes.Mul); 
     il.Emit(OpCodes.Conv_R8); 

     il.Emit(OpCodes.Ldarg_1); 
     il.Emit(OpCodes.Ldc_I4, b); 
     il.Emit(OpCodes.Mul); 
     il.Emit(OpCodes.Conv_R8); 

     il.Emit(OpCodes.Call, log); 

     il.Emit(OpCodes.Sub); 

     il.Emit(OpCodes.Ret); 

     return (Func<int, int, double>)method.CreateDelegate(typeof(Func<int, int, double>)); 
    } 

    static Func<int, int, double> EmbedConstFunc(int b) 
    { 
     return (a, z) => a * b - Math.Log(z * b); 
    } 
} 
+2

不能說我知道,但是這取決於你使用這個東西你可能想看看在自由Fasterflect API:HTTP://fasterflect.codeplex。 com/ –

+1

我在這裏對你的代碼的非正式測試中沒有看到 - mul1(IL版本)在這裏一直更快(大約2倍)。 –

+0

我添加了輸出到問題。 mul2快兩倍。請在x64 Release中構建,然後從控制檯運行。不是從Visual Studio裏面。 –

回答

2

鑑於只有在發佈模式下運行時沒有附加調試器纔會出現性能差異,我能想到的唯一解釋是JIT編譯器能夠爲lambda表達式進行本地代碼優化,使其無法執行發射的IL動態功能。

對於發佈模式(優化開啓)進行編譯並且在沒有附加調試器的情況下運行,lambda始終比生成的IL動態方法快2倍。

使用與進程相連的調試器運行相同的發佈模式優化版本會將lambda表現降至與生成的IL動態方法相當或更差的水平。

這兩次運行的唯一區別在於JIT的行爲。當一個進程正在被調試時,JIT編譯器會抑制一些本地代碼gen優化,以保留對IL指令的本地指令,以保留源代碼行號映射和其他相關性,這些相關性會被侵略性的本機指令優化所破壞。

編譯器只能在輸入表達式圖形(在本例中爲IL代碼)匹配某些非常特定的模式和條件時應用特殊情況優化。 JIT編譯器明確具有對lambda表達式IL代碼模式的特殊知識,並且對於lambda表達式發出的代碼不同於「正常」IL代碼。

你的IL指令很可能並不完全匹配導致JIT編譯器優化lambda表達式的模式。例如,您的IL指令將B值編碼爲內聯常量,而類似的lambda表達式則從內部捕獲的變量對象實例中加載一個字段。即使您生成的IL模仿C#編譯器生成的lambda表達式IL捕獲的字段模式,它仍然可能不夠「足夠接近」以接收與lambda表達式相同的JIT處理。

正如在評論中提到的,這可能是由於lambda的內聯消除了調用/返回開銷。如果是這種情況,我期望看到這種性能差異在更復雜的lambda表達式中消失,因爲內聯通常僅用於最簡單的表達式。

11

恆定5是原因。究竟是爲什麼呢?原因:當JIT知道常數爲5時,它不會發出imul指令,而是發出[rax, rax * 4]指令。這是一個衆所周知的組裝級優化。但由於某種原因,這段代碼執行速度較慢。優化是一種悲觀。

而發出閉包的C#編譯器阻止JIT以特定方式優化代碼。

證明:將常數更改爲56878567並改變性能。在檢查JIT代碼時,您可以看到現在使用了一個imul。

我設法通過硬編碼常數5成這樣的lambda來抓住這個:

static Func<int, int> EmbedConstFunc2(int b) 
    { 
     return a => a * 5; 
    } 

這讓我檢查JIT編譯的x86。

旁註:.NET JIT不以任何方式內聯委託調用。只是提到這一點,因爲這是錯誤的推測這是在評論中的情況。

Sidenode 2:爲了獲得完整的JIT優化級別,您需要在Release模式下進行編譯,並且在沒有附加調試器的情況下啓動。調試器阻止執行優化,即使在發佈模式下也是如此。旁註3:儘管EmbedConstFunc包含一個閉包,通常會比動態生成的方法慢,但這種「優化」優化的效果會造成更多的破壞,最終會變慢。

+0

+1:不錯的地方:)(關閉) – leppie

+1

你正在向後看。生成的方法比lambda表達式慢2倍。您的參數支持生成的方法比lambda表達式更快。 – dthorpe

+0

好吧,好像我倒過來了。我爲x86和x64重申了這一點。我檢查了IL代碼,我所說的* *看起來都是真的。然而,這些數字並不是謊言!嗯...... Visual Studio不允許我調試EmbedConst func的JITed代碼。 – usr

4

lambda不比DynamicMethod快。它基於。但是,靜態方法比實例方法更快,但對於靜態方法的委託創建比委託創建實例方法要慢。 Lambda表達式構建一個靜態方法,但像實例方法一樣使用它作爲第一個paameter添加一個「Closure」。委託靜態方法「流行」堆棧擺脫「mov」之前不需要的「this」實例到真正的「IL主體」。例如在委託的情況下直接命中「IL身體」的方法。這就是爲什麼通過lambda表達式構建一個直觀的靜態方法的委託會更快(也許是委託模式代碼在實例/靜態方法之間共享的副作用)

性能問題可以通過添加一個未使用的第一個參數例如閉包類型)轉換爲DynamicMethod並用顯式目標實例調用CreateDelegate(可以使用null)。

var myDelegate = DynamicMethod.CreateDelegate(MyDelegateType,null)as MyDelegateType;

http://msdn.microsoft.com/fr-fr/library/z43fsh67(v=vs.110).aspx

託尼THONG

相關問題