2016-02-16 10 views
17

Đối với trình mô phỏng dịch nhị phân động, tôi cần tạo các assembly thu thập được với các lớp truy cập các trường tĩnh. Tuy nhiên, khi sử dụng các trường tĩnh bên trong các cụm thu, hiệu suất thực hiện là thấp hơn 2-3 so với các cụm không thu được. Hiện tượng này không có trong các cụm thu được không sử dụng các trường tĩnh.Truy cập trường tĩnh trong các cụm động thu được thiếu hiệu suất

Trong mã bên dưới phương pháp MyMethod của lớp trừu tượng AbstrTest được triển khai bởi các cụm động có thể thu và không thu được. Sử dụng CreateTypeConst các MyMethod nhân giá trị đối số ulong bởi một giá trị không đổi của hai, trong khi sử dụng CreateTypeField yếu tố thứ hai được lấy từ một trường khởi tạo hàm khởi tạo tĩnh MyField.

Để có được kết quả thực tế, kết quả MyMethod được tích lũy trong vòng lặp for.

Sau đây là các kết quả đo lường (.NET CLR 4.5/4.6):

Testing non-collectible const multiply: 
Elapsed: 8721.2867 ms 

Testing collectible const multiply: 
Elapsed: 8696.8124 ms 

Testing non-collectible field multiply: 
Elapsed: 10151.6921 ms 

Testing collectible field multiply: 
Elapsed: 33404.4878 ms 

Đây là mã người mô phỏng của tôi:

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

public abstract class AbstrTest { 
    public abstract ulong MyMethod(ulong x); 
} 

public class DerivedClassBuilder { 

    private static Type CreateTypeConst(string name, bool collect) { 
    // Create an assembly. 
    AssemblyName myAssemblyName = new AssemblyName(); 
    myAssemblyName.Name = name; 
    AssemblyBuilder myAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(
     myAssemblyName, collect ? AssemblyBuilderAccess.RunAndCollect : AssemblyBuilderAccess.Run); 

    // Create a dynamic module in Dynamic Assembly. 
    ModuleBuilder myModuleBuilder = myAssembly.DefineDynamicModule(name); 

    // Define a public class named "MyClass" in the assembly. 
    TypeBuilder myTypeBuilder = myModuleBuilder.DefineType("MyClass", TypeAttributes.Public, typeof(AbstrTest)); 

    // Create the MyMethod method. 
    MethodBuilder myMethodBuilder = myTypeBuilder.DefineMethod("MyMethod", 
     MethodAttributes.Public | MethodAttributes.ReuseSlot | MethodAttributes.Virtual | MethodAttributes.HideBySig, 
     typeof(ulong), new Type [] { typeof(ulong) }); 
    ILGenerator methodIL = myMethodBuilder.GetILGenerator(); 
    methodIL.Emit(OpCodes.Ldarg_1); 
    methodIL.Emit(OpCodes.Ldc_I4_2); 
    methodIL.Emit(OpCodes.Conv_U8); 
    methodIL.Emit(OpCodes.Mul); 
    methodIL.Emit(OpCodes.Ret); 

    return myTypeBuilder.CreateType(); 
    } 

    private static Type CreateTypeField(string name, bool collect) { 
    // Create an assembly. 
    AssemblyName myAssemblyName = new AssemblyName(); 
    myAssemblyName.Name = name; 
    AssemblyBuilder myAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(
     myAssemblyName, collect ? AssemblyBuilderAccess.RunAndCollect : AssemblyBuilderAccess.Run); 

    // Create a dynamic module in Dynamic Assembly. 
    ModuleBuilder myModuleBuilder = myAssembly.DefineDynamicModule(name); 

    // Define a public class named "MyClass" in the assembly. 
    TypeBuilder myTypeBuilder = myModuleBuilder.DefineType("MyClass", TypeAttributes.Public, typeof(AbstrTest)); 

    // Define a private String field named "MyField" in the type. 
    FieldBuilder myFieldBuilder = myTypeBuilder.DefineField("MyField", 
     typeof(ulong), FieldAttributes.Private | FieldAttributes.Static); 

    // Create the constructor. 
    ConstructorBuilder constructor = myTypeBuilder.DefineConstructor(
     MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | MethodAttributes.HideBySig, 
     CallingConventions.Standard, Type.EmptyTypes); 
    ConstructorInfo superConstructor = typeof(AbstrTest).GetConstructor(
     BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, 
     null, Type.EmptyTypes, null); 
    ILGenerator constructorIL = constructor.GetILGenerator(); 
    constructorIL.Emit(OpCodes.Ldarg_0); 
    constructorIL.Emit(OpCodes.Call, superConstructor); 
    constructorIL.Emit(OpCodes.Ldc_I4_2); 
    constructorIL.Emit(OpCodes.Conv_U8); 
    constructorIL.Emit(OpCodes.Stsfld, myFieldBuilder); 
    constructorIL.Emit(OpCodes.Ret); 

    // Create the MyMethod method. 
    MethodBuilder myMethodBuilder = myTypeBuilder.DefineMethod("MyMethod", 
     MethodAttributes.Public | MethodAttributes.ReuseSlot | MethodAttributes.Virtual | MethodAttributes.HideBySig, 
     typeof(ulong), new Type [] { typeof(ulong) }); 
    ILGenerator methodIL = myMethodBuilder.GetILGenerator(); 
    methodIL.Emit(OpCodes.Ldarg_1); 
    methodIL.Emit(OpCodes.Ldsfld, myFieldBuilder); 
    methodIL.Emit(OpCodes.Mul); 
    methodIL.Emit(OpCodes.Ret); 

    return myTypeBuilder.CreateType(); 
    } 

    public static void Main() { 
    ulong accu; 
    Stopwatch stopwatch; 
    try { 
     Console.WriteLine("Testing non-collectible const multiply:"); 
     AbstrTest i0 = (AbstrTest)Activator.CreateInstance(
     CreateTypeConst("MyClassModule0", false)); 
     stopwatch = Stopwatch.StartNew(); 
     accu = 0; 
     for (uint i = 0; i < 0xffffffff; i++) 
     accu += i0.MyMethod(i); 
     stopwatch.Stop(); 
     Console.WriteLine("Elapsed: " + stopwatch.Elapsed.TotalMilliseconds + " ms"); 

     Console.WriteLine("Testing collectible const multiply:"); 
     AbstrTest i1 = (AbstrTest)Activator.CreateInstance(
     CreateTypeConst("MyClassModule1", true)); 
     stopwatch = Stopwatch.StartNew(); 
     accu = 0; 
     for (uint i = 0; i < 0xffffffff; i++) 
     accu += i1.MyMethod(i); 
     stopwatch.Stop(); 
     Console.WriteLine("Elapsed: " + stopwatch.Elapsed.TotalMilliseconds + " ms"); 

     Console.WriteLine("Testing non-collectible field multiply:"); 
     AbstrTest i2 = (AbstrTest)Activator.CreateInstance(
     CreateTypeField("MyClassModule2", false)); 
     stopwatch = Stopwatch.StartNew(); 
     accu = 0; 
     for (uint i = 0; i < 0xffffffff; i++) 
     accu += i2.MyMethod(i); 
     stopwatch.Stop(); 
     Console.WriteLine("Elapsed: " + stopwatch.Elapsed.TotalMilliseconds + " ms"); 

     Console.WriteLine("Testing collectible field multiply:"); 
     AbstrTest i3 = (AbstrTest)Activator.CreateInstance(
     CreateTypeField("MyClassModule3", true)); 
     stopwatch = Stopwatch.StartNew(); 
     accu = 0; 
     for (uint i = 0; i < 0xffffffff; i++) 
     accu += i3.MyMethod(i); 
     stopwatch.Stop(); 
     Console.WriteLine("Elapsed: " + stopwatch.Elapsed.TotalMilliseconds + " ms"); 
    } 
    catch (Exception e) { 
     Console.WriteLine("Exception Caught " + e.Message); 
    } 
    } 
} 

Vì vậy, câu hỏi của tôi là: Tại sao nó chậm hơn?

Trả lời

12

Vâng, đây là một hậu quả không thể tránh khỏi của các biến tĩnh được phân bổ. Lần đầu tiên tôi sẽ mô tả cách bạn đưa "trực quan" trở lại Visual Studio, bạn sẽ chỉ có một shot tại chẩn đoán các vấn đề perf như thế này khi bạn có thể nhìn vào mã máy mà jitter tạo ra.

Đó là khó khăn để làm cho Reflection.Emit mã, bạn không thể bước qua các cuộc gọi đại biểu cũng không làm bạn có bất kỳ cách nào để tìm chính xác nơi mã được tạo ra. Những gì bạn muốn làm là tiêm một cuộc gọi đến Debugger.Break() để trình gỡ rối dừng lại ở đúng vị trí chính xác. Vì vậy:

ILGenerator methodIL = myMethodBuilder.GetILGenerator(); 
    var brk = typeof(Debugger).GetMethod("Break"); 
    methodIL.Emit(OpCodes.Call, brk); 
    methodIL.Emit(OpCodes.Ldarg_1); 
    // etc.. 

Thay đổi vòng lặp lặp lại thành 1. Công cụ> Tùy chọn> Gỡ lỗi> Chung. Untick "Just My Code" và "Suppress JIT optimization". Tab gỡ lỗi> đánh dấu chọn "Bật gỡ lỗi mã gốc". Chuyển sang bản dựng. Tôi sẽ đăng các mã 32-bit, nó là thú vị hơn kể từ khi jitter x64 có thể làm một công việc tốt hơn nhiều.

Mã máy cho "Thử nghiệm lĩnh vực phi thu nhân" thử nghiệm trông giống như:

01410E70 push  dword ptr [ebp+0Ch]  ; Ldarg_1, high 32-bits 
01410E73 push  dword ptr [ebp+8]   ; Ldarg_1, low 32-bits 
01410E76 push  dword ptr ds:[13A6528h] ; myFieldBuilder, high 32-bits 
01410E7C push  dword ptr ds:[13A6524h] ; myFieldBuilder, low 32-bits 
01410E82 call  @[email protected] (73AE1C20h) ; 64 bit multiply 

Không có gì rất quyết liệt xảy ra, nó gọi vào một phương pháp CLR helper để thực hiện một 64-bit nhân. Trình kích hoạt x64 có thể thực hiện nó với một lệnh IMUL đơn lẻ. Lưu ý truy cập vào biến tĩnh myFieldBuilder, nó có địa chỉ mã hóa cứng, 0x13A6524. Nó sẽ khác trên máy của bạn. Điều này rất hiệu quả.

Bây giờ gây thất vọng nhất:

059F0480 push  dword ptr [ebp+0Ch]  ; Ldarg_1, high 32-bits 
059F0483 push  dword ptr [ebp+8]   ; Ldarg_1, low 32-bits 
059F0486 mov   ecx,59FC8A0h    ; arg2 = DynamicClassDomainId 
059F048B xor   edx,edx     ; arg1 = DomainId 
059F048D call  JIT_GetSharedNonGCStaticBaseDynamicClass (73E0A6C7h) 
059F0492 push  dword ptr [eax+8]   ; @myFieldBuilder, high 32-bits 
059F0495 push  dword ptr [eax+4]   ; @myFieldBuilder, low 32-bits 
059F0498 call  @[email protected] (73AE1C20h) ; 64-bit multiply 

Bạn có thể nói lý do tại sao nó chậm hơn từ nửa dặm, có một cuộc gọi thêm để JIT_GetSharedNonGCStaticBaseDynamicClass. Nó là một hàm trợ giúp bên trong CLR được thiết kế đặc biệt để xử lý các biến tĩnh được sử dụng trong mã Reflection.Emit được xây dựng với AssemblyBuilderAccess.RunAndCollect. Bạn có thể xem nguồn ngày hôm nay, nó is here.Làm cho mắt của mọi người bị chảy máu nhưng đó là chức năng ánh xạ một định danh AppDomain và một định danh lớp động (còn gọi là xử lý kiểu) cho một phần bộ nhớ được phân bổ lưu trữ các biến tĩnh.

Trong phiên bản "không thể thu thập", jitter biết địa chỉ cụ thể nơi biến tĩnh được lưu trữ. Nó phân bổ biến khi nó jitted mã từ một cấu trúc nội bộ được gọi là "bộ nạp đống", được liên kết với AppDomain. Biết địa chỉ chính xác của biến, nó có thể trực tiếp phát ra địa chỉ của biến trong mã máy. Rất hiệu quả tất nhiên, không có cách nào có thể để làm điều này nhanh hơn.

Nhưng điều đó không thể hoạt động trong phiên bản "có thể sưu tập", nó không chỉ phải thu gom mã máy nhưng cũng biến tĩnh. Điều đó chỉ có thể hoạt động khi lưu trữ được phân bổ động. Vì vậy, nó có thể tự động được phát hành. Sự gián tiếp thêm, so sánh nó với một từ điển, là những gì làm cho mã chậm hơn.

Bây giờ bạn có thể sẽ đánh giá cao lý do tại sao không thể dỡ tải các assembly .NET (và mã) trừ khi AppDomain không được tải. Đó là một sự tối ưu hóa rất quan trọng.

Bạn không chắc chắn nên giới thiệu loại đề xuất nào. Người ta có thể tự chăm sóc lưu trữ biến tĩnh, một lớp với các trường mẫu. Không có vấn đề gì với những người được thu thập. Tuy nhiên sẽ không nhanh như vậy, nó sẽ mất thêm một sự gián đoạn, nhưng chắc chắn nhanh hơn là để CLR chăm sóc nó.

+0

Đó là những gì tôi đã đề xuất trong đoạn cuối. Không tĩnh, các instance fields của một class, bạn sẽ chỉ có một instance của đối tượng class. Bạn sẽ phải sử dụng nó trong các cuộc gọi ILGenerator.Emit() của bạn. Và sử dụng GCHandle.Alloc() để đảm bảo nó vẫn hoạt động cho đến khi mã được thu thập. –

+0

Tôi nghĩ rằng hạn chế cốt lõi là jitter không thể giả định một cách an toàn rằng một biến tĩnh sẽ bị hủy hoặc thiết lập lại khi thu thập tập hợp được thu thập. Có nó giữ lại một tham chiếu đến một đối tượng lớp mà mã đã biến mất là thảm họa và có thể khai thác được. Một cái gì đó như thế. "Tại sao" sẽ không hữu ích để giải quyết vấn đề của bạn tất nhiên. –

+0

Câu trả lời hay. Cảm ơn rất nhiều. – Paebbels

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