2011-05-05 3 views
39

나는 Reflection.Emit에서 놀고 있었는데, 거의 사용되지 않은 EmitCalli에 대해 발견했다. 호기심, 나는 그것이 일반 메소드 호출에서 어떤 다른 있는지 궁금 그래서 아래의 코드를 채찍질 :Calli가 대리모 통화보다 빠른 이유는 무엇입니까?

using System; 
using System.Diagnostics; 
using System.Reflection.Emit; 
using System.Runtime.InteropServices; 
using System.Security; 

[SuppressUnmanagedCodeSecurity] 
static class Program 
{ 
    const long COUNT = 1 << 22; 
    static readonly byte[] multiply = IntPtr.Size == sizeof(int) ? 
     new byte[] { 0x8B, 0x44, 0x24, 0x04, 0x0F, 0xAF, 0x44, 0x24, 0x08, 0xC3 } 
    : new byte[] { 0x0f, 0xaf, 0xca, 0x8b, 0xc1, 0xc3 }; 

    static void Main() 
    { 
     var handle = GCHandle.Alloc(multiply, GCHandleType.Pinned); 
     try 
     { 
      //Make the native method executable 
      uint old; 
      VirtualProtect(handle.AddrOfPinnedObject(), 
       (IntPtr)multiply.Length, 0x40, out old); 
      var mulDelegate = (BinaryOp)Marshal.GetDelegateForFunctionPointer(
       handle.AddrOfPinnedObject(), typeof(BinaryOp)); 

      var T = typeof(uint); //To avoid redundant typing 

      //Generate the method 
      var method = new DynamicMethod("Mul", T, 
       new Type[] { T, T }, T.Module); 
      var gen = method.GetILGenerator(); 
      gen.Emit(OpCodes.Ldarg_0); 
      gen.Emit(OpCodes.Ldarg_1); 
      gen.Emit(OpCodes.Ldc_I8, (long)handle.AddrOfPinnedObject()); 
      gen.Emit(OpCodes.Conv_I); 
      gen.EmitCalli(OpCodes.Calli, CallingConvention.StdCall, 
       T, new Type[] { T, T }); 
      gen.Emit(OpCodes.Ret); 

      var mulCalli = (BinaryOp)method.CreateDelegate(typeof(BinaryOp)); 

      var sw = Stopwatch.StartNew(); 
      for (int i = 0; i < COUNT; i++) { mulDelegate(2, 3); } 
      Console.WriteLine("Delegate: {0:N0}", sw.ElapsedMilliseconds); 
      sw.Reset(); 

      sw.Start(); 
      for (int i = 0; i < COUNT; i++) { mulCalli(2, 3); } 
      Console.WriteLine("Calli: {0:N0}", sw.ElapsedMilliseconds); 
     } 
     finally { handle.Free(); } 
    } 

    delegate uint BinaryOp(uint a, uint b); 

    [DllImport("kernel32.dll", SetLastError = true)] 
    static extern bool VirtualProtect(
     IntPtr address, IntPtr size, uint protect, out uint oldProtect); 
} 

내가 86 모드 및 x64 모드에서 코드를 실행했습니다. 결과는?

32 비트 :

  • 위임 버전 : 994
  • 캘러스 버전 : 46

64 비트 :

  • 위임 버전 : 326
  • Calli 버전 : 83

나는 지금 당장 의문의 여지가 ... 왜 그렇게 큰 속도 차이가 나는지?


업데이트 :

  • 위임 버전 : 284
  • 캘러스 판 77
  • 내가 아니라 64 비트 P/호출 버전을 생성 P/Invoke 버전 : 31

명백히 P/Invoke가 더 빠릅니다 ... 내 벤치마킹에 문제가 있습니까? 아니면 이해가 안되는 부분이 있습니까? (나는 그런데 해제 모드에있다.)

+0

+1, 좋은 질문 :) –

+0

매우 흥미로운 질문입니다. 나도 기계에서 시도하고 큰 속도 차이가 있습니다. 나는 또한 그 뒤에 정확한 이유를 알고 싶다. –

+0

실제로 벤치마킹이 잘못되었다고 의심하기 시작했습니다. 중간에 내가 눈치 채지 못하는 지시 사항이있을 수 있습니다. 결과가 엉망입니다. 지금은 많이 생각할 수는 없지만 ... – Mehrdad

답변

4

대답하기가 어렵다. : 어쨌든 나는 시도 할 것이다.

EmitCalli는 원시 바이트 코드 호출이기 때문에 빠릅니다. 나는 SuppressUnmanagedCodeSecurity가 예를 들어 stack overrun/array out of bounds 인덱스 검사와 같은 일부 검사를 비활성화 할 것이라고 생각합니다. 따라서 코드는 안전하지 않으며 최고 속도로 실행됩니다.

대리자 버전은 입력을 검사 할 수있는 컴파일 된 코드가 있으며 대리자는 입력 된 함수 포인터와 유사하므로 참조 해제 호출도 수행합니다.

내 두 센트!

+0

흠 ... 혼란 스러워요. 두 버전 모두 대리인을 사용합니다. 대리인이 어떻게 만들어 졌는지에 차이가 있습니다. 그 점에서 둘 다 똑같지 않아야합니다. – Mehrdad

+0

또한,'SuppressUnmanagedCodeSecurity'는 유형 검사 안전을 비활성화하지 않으며, 관리 - 비 관리 전환으로부터 스택 워크를 수행합니다. 관리되지 않는 코드 권한입니다. 관련이없는 병목 현상을 없애기 위해 거기에 넣었습니다. – Mehrdad

+0

@ mehrdad : 네 말이 맞지만 속도는 영향을받습니다. 우리는 http://msdn.microsoft.com/en-us/library/system.security.suppressunmanagedcodesecurityattribute.aspx에서이 특성을 읽을 수 있습니다. " 그렇게 할 때 런타임 보안 검사의 성능 손실을 초래하지 않으면 서 원시 코드로 호출 할 수 있습니다. " – daitangio

6

성능 수치가 주어지면 2.0 프레임 워크 또는 이와 비슷한 것을 사용하고 있어야한다고 가정합니까? 이 수치는 4.0에서 훨씬 좋아졌지만 "Marshal.GetDelegate"버전은 여전히 ​​느립니다.

모든 대의원이 동일하게 생성되는 것은 아닙니다.

관리 코드 함수의 대리자는 본질적으로 정적 함수를 호출하는 경우 약간의 "switcheroo"가 추가 된 (x86에서는 __fastcall에서의) 직선 함수 호출입니다 (단 3 개 또는 4 개의 명령어 x86에서).

"Marshal.GetDelegateForFunctionPointer"로 만든 대리인은 관리되지 않는 함수를 호출하기 전에 약간의 오버 헤드 (마샬링 및 기타 등등)를 수행하는 "스텁"함수로의 직접 함수 호출입니다. 이 경우에는 마샬링이 거의 없으며이 호출에 대한 마샬링은 4.0에서 거의 최적화 된 것처럼 보입니다 (하지만 2.0에서는 ML 인터프리터를 통과 할 가능성이 가장 높습니다).하지만 4.0에서도 관리되지 않는 코드 사용 권한을 요구하는 stackWalk가 있습니다. calli 대리인의 일부가 아닙니다.

저는 .NET 개발자 팀에서 누군가를 알지 못하면서 WinDbg 및 SOS를 조금 파헤 치면됩니다. 관리되는/관리되지 않는 interop에서 진행되는 작업을 파악하는 것이 가장 좋습니다.

+0

이 남자는 그가 말하는 것을 알고있다! +1000 –