2011-12-31 51 views
6

考慮下面的代碼:的ILGenerator方法內聯

using System; 
using System.Reflection.Emit; 
using System.Diagnostics; 
using System.Reflection; 

namespace ConsoleApplication1 
{ 
    class A 
    { 
     public int Do(int n) 
     { 
      return n; 
     } 
    } 

    public delegate int DoDelegate(); 

    class Program 
    { 
     public static void Main(string[] args) 
     { 
      A a = new A(); 

      Stopwatch stopwatch = Stopwatch.StartNew(); 
      int s = 0; 
      for (int i = 0; i < 100000000; i++) 
      { 
       s += a.Do(i); 
      } 

      Console.WriteLine(stopwatch.ElapsedMilliseconds); 
      Console.WriteLine(s); 


      DynamicMethod dm = new DynamicMethod("Echo", typeof(int), new Type[] { typeof(int) }, true); 
      ILGenerator il = dm.GetILGenerator(); 

      il.Emit(OpCodes.Ldarg_0); 
      il.Emit(OpCodes.Ret); 

      DynamicMethod dm2 = new DynamicMethod("Test", typeof(int), new Type[0]); 
      il = dm2.GetILGenerator(); 


      Label loopStart = il.DefineLabel(); 
      Label loopCond = il.DefineLabel(); 

      il.DeclareLocal(typeof(int)); // i 
      il.DeclareLocal(typeof(int)); // s 

      // s = 0; 
      il.Emit(OpCodes.Ldc_I4_0); 
      il.Emit(OpCodes.Stloc_1); 

      // i = 0; 
      il.Emit(OpCodes.Ldc_I4_0); 
      il.Emit(OpCodes.Stloc_0); 

      il.Emit(OpCodes.Br_S, loopCond); 

      il.MarkLabel(loopStart); 

      // s += Echo(i); 
      il.Emit(OpCodes.Ldloc_1); // Load s 
      il.Emit(OpCodes.Ldloc_0); // Load i 
      il.Emit(OpCodes.Call, dm); // Call echo method 
      il.Emit(OpCodes.Add); 
      il.Emit(OpCodes.Stloc_1); 

      // i++ 
      il.Emit(OpCodes.Ldloc_0); 
      il.Emit(OpCodes.Ldc_I4_1); 
      il.Emit(OpCodes.Add); 
      il.Emit(OpCodes.Stloc_0); 

      il.MarkLabel(loopCond); 

      // Check for loop condition 
      il.Emit(OpCodes.Ldloc_0); 
      il.Emit(OpCodes.Ldc_I4, 100000000); 
      il.Emit(OpCodes.Blt_S, loopStart); 

      il.Emit(OpCodes.Ldloc_1); 
      il.Emit(OpCodes.Ret); 


      DoDelegate doDel = (DoDelegate)dm2.CreateDelegate(typeof(DoDelegate)); 
      s = doDel.Invoke();  // Dummy run to force JIT 


      stopwatch = Stopwatch.StartNew(); 
      s = doDel.Invoke(); 
      Console.WriteLine(stopwatch.ElapsedMilliseconds); 
      Console.WriteLine(s); 
     } 
    } 
} 

電話方式,否則被內聯。循環在約40毫秒內完成。例如,如果我將Do設爲虛擬函數,則它不會被內聯,並且循環在240 ms內完成。到現在爲止還挺好。當我使用ILGenerator生成Do方法(Echo),然後使用與給定主方法相同的循環生成DynamicMethod時,調用Echo方法永遠不會內聯,並且循環完成需要約240 ms。 MSIL代碼是正確的,因爲它返回與C#代碼相同的結果。我確信方法內聯是由JIT完成的,所以我沒有理由不內聯Echo方法。

有人知道爲什麼這個簡單的方法不會被JIT內聯。

+0

您還正在生成調用動態生成的Do()方法的代碼,還是編譯時已知的代碼? – 2011-12-31 00:12:48

+0

您能否包含使用ILGenerator的完整代碼示例?而且,爲了確保:您是否在發佈版本**下進行測試,而沒有附加調試器? – 2011-12-31 05:37:56

+0

我已重新編輯帖子,給出測試應用程序的完整代碼。我使用發佈版本並在沒有調試器的情況下運行它。 C#for循環內聯方法調用,運行速度明顯快於IL for循環。 – user102808 2011-12-31 09:22:26

回答

0

如果我的理解正確,我的猜測是由於該方法是動態生成的,JIT編譯器不知道爲調用者內聯它。

我已經寫了大量的IL,但是我沒有研究過內聯行爲(主要是因爲動態方法對於我的目的通常足夠快而沒有進一步優化)。

我希望有人對這個主題有更多的瞭解,並給予反饋(請不要只是低估;如果我錯了,我想在這裏學習一些東西)。

非動態

  • 你寫的 「正常」 的.NET代碼(如C#,VB.NET,任何CLS感知語言)
  • IL是在編譯時
  • 創建機器代碼在運行時創建;方法是內聯酌情

動態

  • 你寫的「正常」的.NET代碼,其目的是創建一個動態方法
  • IL是在編譯時該代碼創建的,但動態方法不會創建
  • 機器代碼在運行時爲代碼生成動態方法
  • 當代碼被調用時,動態符合hod在特殊程序集中創建爲IL
  • 動態方法的IL編譯爲機器代碼
  • 但是,JIT編譯器不會重新編譯其他調用程序以內聯新的動態方法。或者也許其他呼叫者本身是動態的並且還沒有被創建。
+0

我添加了重現問題的富勒示例代碼。正如你所看到的,我產生了兩種動態方法。一個非常簡單,只是返回它作爲參數獲得的相同值。另一種方法運行一個簡單的for循環(與開始時的C#代碼中的一樣)。我不希望JIT內聯調用第二種方法,因爲它是通過委託調用的。但是,我確實希望他在第二種方法中調用時插入第一種方法。當我自己內聯時,相同循環的執行速度提高了10倍。 – user102808 2011-12-31 09:26:28

1

經過進一步調查我的結論如下:

  1. 的ILGenerator生成的方法永遠不會內聯。無論您是使用委託,使用其他DynamicMethod還是使用MethodBuilder創建的方法調用它們,都無關緊要。
  2. 只有在用MethodBuilder創建的方法調用時,現有方法(用C#編碼並由VS編譯的方法)才能內聯。如果從DynamicMethod調用,它們將永遠不會被內聯。

在徹底測試了很多示例並查看了最終的彙編代碼之後,我總結了這一點。

+0

直到有人表現不同,我認爲這是一個準確的結論。不內聯動態方法有時是有意義的(這是我在我的回覆中提出的建議)。也許編譯器設計者認爲將這視爲所有情況下的規則都是最簡單的。我不知道表達式樹是否有相同的行爲:http://msdn.microsoft.com/en-us/library/bb397951.aspx – 2012-01-01 04:59:02

+0

這將是有趣的檢查,雖然我沒有時間(或需要)玩與表達樹。 – user102808 2012-01-01 10:16:08

+0

當然這也包括IL代碼生成和保存爲DLL並保存後加載?因爲我確定它不包含已保存的生成裝配。無論如何,如果你的答案是正確的,你可以把它標記爲正確的。 – 2012-02-27 14:38:49

0

您可能會嘗試生成動態裝配。我的理解是,大多數運行時不知道它是動態的。我認爲它在內部像其他任何byte []程序集一樣被加載。因此JIT /內聯可能也不知道它(或不在意)。

+0

我甚至試過。不知何故,出於某種原因,動態生成的方法(不管你如何生成它)不會被內聯。不管怎麼說,還是要謝謝你。 – user102808 2012-01-01 10:14:33

+0

很高興知道。 。 – usr 2012-01-01 10:30:14