2011-12-31 29 views
6

Với đoạn mã sau:ILGenerator phương pháp nội tuyến

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); 
     } 
    } 
} 

Gọi phương pháp Đừng bị inlined. Vòng lặp kết thúc trong khoảng 40 ms. Nếu tôi, ví dụ, làm cho Do là chức năng ảo, nó không nhận được nội tuyến, và vòng lặp kết thúc trong 240 ms. Càng xa càng tốt. Khi tôi sử dụng ILGenerator để tạo ra phương thức Do (Echo), và sau đó tạo DynamicMethod với cùng vòng lặp như phương thức chính đã cho, gọi phương thức Echo không bao giờ được inlined và mất khoảng 240 ms cho vòng lặp kết thúc. Mã MSIL là chính xác vì nó trả về kết quả tương tự như mã C#. Tôi đã chắc chắn rằng phương pháp nội tuyến là một cái gì đó được thực hiện bởi JIT, vì vậy tôi thấy không có lý do cho nó không để nội tuyến phương pháp Echo.

Có ai biết tại sao phương pháp đơn giản này sẽ không được JIT giới thiệu.

+0

Bạn cũng đang tạo mã gọi phương thức Do() được tạo động hay là mã đó được biết đến lúc biên dịch? –

+0

Bạn có thể bao gồm mẫu mã đầy đủ sử dụng ILGenerator không? Và, chỉ để chắc chắn: bạn có đang thử nghiệm dưới bản phát hành ** không ** kèm theo trình sửa lỗi không? –

+0

Tôi đã chỉnh sửa lại bài đăng, cung cấp mã đầy đủ cho ứng dụng thử nghiệm. Tôi sử dụng phát hành xây dựng và chạy nó mà không có trình gỡ rối. C# cho vòng lặp inline gọi phương thức và chạy nhanh hơn đáng kể so với vòng lặp IL. – user102808

Trả lời

0

Nếu tôi hiểu chính xác, tôi đoán là vì phương pháp được tạo động, trình biên dịch JIT không biết đặt nội tuyến cho người gọi.

Tôi đã viết rất nhiều IL nhưng tôi chưa xem xét hành vi nội tuyến (chủ yếu vì phương pháp động thường đủ nhanh cho mục đích của tôi mà không cần tối ưu hóa thêm).

Tôi sẽ chào đón một người hiểu biết nhiều hơn về chủ đề này để cung cấp phản hồi (xin vui lòng không chỉ downvote; Tôi muốn tìm hiểu điều gì đó ở đây nếu tôi sai).

Non-động

  • bạn viết "bình thường" mã NET (ví dụ như C#, VB.NET, bất kỳ ngôn ngữ CLS-aware)
  • IL được tạo ra tại thời gian biên dịch
  • mã máy được tạo khi chạy; phương pháp đều được sắp xếp nơi thích hợp

động

  • bạn viết "bình thường" mã NET mà mục đích là tạo ra một phương pháp năng động
  • IL được tạo ra tại thời gian biên dịch cho mã này, nhưng phương pháp động không được tạo
  • mã máy được tạo khi chạy cho mã tạo phương thức động
  • khi mã đó được gọi, đáp ứng động hod được tạo ra như IL trong một assembly đặc biệt
  • IL của phương thức động được biên dịch thành mã máy
  • tuy nhiên, trình biên dịch JIT không biên dịch lại những người gọi khác để nội tuyến phương thức động mới. Hoặc có lẽ những người gọi khác là năng động và chưa được tạo ra.
+0

Tôi đã thêm mã mẫu fulle tái tạo sự cố. Như bạn có thể thấy, tôi tạo ra hai phương thức động. Một là đơn giản, và chỉ trả về cùng giá trị mà nó nhận được như một param. Phương thức khác chạy một vòng lặp đơn giản (giống như trong mã C# lúc bắt đầu). Tôi không mong đợi JIT để nội tuyến cuộc gọi đến phương pháp thứ hai bởi vì nó được gọi là thông qua một đại biểu. Howerer, tôi hy vọng anh ta để nội tuyến phương pháp đầu tiên, khi nó được gọi là bên trong phương pháp thứ hai. Khi tôi tự nó, tôi nhận được 10 lần thực hiện nhanh hơn của cùng một vòng lặp. – user102808

1

Sau khi điều tra thêm tôi đã kết luận như sau: phương pháp tạo

  1. ILGenerator sẽ không bao giờ được inlined. Cho dù bạn gọi họ bằng cách sử dụng delegate, từ một DynamicMethod khác hoặc từ một phương thức được tạo bằng MethodBuilder thì cũng không thành vấn đề.
  2. Các phương thức hiện có (các phương thức được mã hóa trong C# và được biên dịch bởi VS) chỉ có thể được đặt nội tuyến khi được gọi từ một phương thức được tạo bằng MethodBuilder.Nếu được gọi từ DynamicMethod, chúng sẽ không bao giờ được inlined.

Tôi đã kết luận điều này sau khi kiểm tra kỹ lưỡng nhiều mẫu và xem xét mã lắp ráp cuối cùng.

+0

Cho đến khi ai đó thể hiện một cách khác nhau, tôi nghĩ đây là một kết luận chính xác. Không nội tuyến Các phương pháp động đôi khi có ý nghĩa (đó là những gì tôi đề xuất trong phản hồi của tôi). Có lẽ các nhà thiết kế trình biên dịch đã quyết định rằng nó sẽ đơn giản nhất để coi đây là một quy tắc cho tất cả các trường hợp. Tôi tự hỏi nếu cây biểu hiện hành xử theo cùng một cách: http://msdn.microsoft.com/en-us/library/bb397951.aspx –

+0

Điều đó sẽ rất thú vị để kiểm tra, mặc dù tôi không có thời gian (hoặc cần) để chơi với cây biểu thức. – user102808

+0

Chắc chắn điều này cũng bao gồm mã IL được tạo và lưu dưới dạng dll và được tải sau khi lưu? Bởi vì tôi chắc chắn nó không bao gồm các hội đồng được tạo ra đã được lưu lại. Dù sao nếu câu trả lời của bạn là chính xác, bạn chỉ có thể đánh dấu nó là chính xác. –

0

Bạn có thể thử tạo một cụm động. Sự hiểu biết của tôi là hầu hết thời gian chạy không nhận thức được rằng nó là động. Tôi nghĩ rằng nội bộ nó được nạp giống như bất kỳ assembly nào khác []. Do đó JIT/inliner cũng có thể không nhận thức được nó (hoặc sẽ không quan tâm).

+0

Tôi thậm chí đã thử điều đó. Bằng cách nào đó và vì lý do nào đó, phương pháp được tạo động (không có vấn đề gì bạn tạo ra nó) sẽ không được inlined. Dù sao cũng cảm ơn bạn. – user102808

+0

Điều cần biết. . – usr

Các vấn đề liên quan