メソッドの呼び出し時間

http://d.hatena.ne.jp/akiramei/20040518#p1こちらもご覧ください。

C#ではP/InvokeによってUnmanaged関数を簡単に呼び出すことが出来ます。P/Invokeされる関数は恐らくC/C++などで作られているので大抵高速です。ですが、C#からUnmanaged関数を呼び出すコストもあるはずです。資料によると「8命令で高速に呼び出せる」とありますが、これはどの程度高速なのでしょうか?と、いうことで調査してみました。
以下、テストコード。

Reflection.Emitでは上手く行かなかったのでILで代用した関数ポインタを呼び出すアセンブリ(CalcIL.dll)。

.assembly extern mscorlib {} 
.assembly CalcIL{} 

.class public CalcIL 
{
     .method public static int32 AddIL(native int pfn, int32 x, int32 y)  
    { 
        .maxstack 3 
        ldarg.1 
        ldarg.2 
        ldarg.0 
        calli unmanaged stdcall int32(int32,int32) 
        ret 
    } 
} 

色々な関数呼び出しの速度を比較するテストモジュール。

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

// Emit経由で関数ポインタを呼ぶ為
public interface ICalc {
  int EmitAdd(IntPtr pf, int x, int y);
}

class App {
  const int LOOP = 100000000;

  [DllImport("kernel32")]
  public extern static IntPtr LoadLibrary(string lpLibFileName);

  [DllImport("kernel32")]
  public extern static bool FreeLibrary(IntPtr hLibModule);

  [DllImport("kernel32")]
  public extern static IntPtr GetProcAddress(IntPtr hModule,string lpProcName);

  [DllImport("Calc.dll", EntryPoint="Add")]
  public static extern int AddDLL(int x, int y);

  public static ICalc GetEmitClass() {
    AssemblyName asnm = new AssemblyName();
    asnm.Name = "EmitAssembly";
    AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(
      asnm, AssemblyBuilderAccess.Run);
    ModuleBuilder md = ab.DefineDynamicModule("EmitModule");
    // クラスを作成
    TypeBuilder tb = md.DefineType("EmitClass", 
      TypeAttributes.Public);
    // ICalcを実装させる
    tb.AddInterfaceImplementation(typeof(ICalc));

    // EmitAddの実装
    MethodBuilder mt = tb.DefineMethod("EmitAdd",
      MethodAttributes.Virtual | MethodAttributes.Public, 
      typeof(int), new Type {typeof(IntPtr), typeof(int), typeof(int)});
    ILGenerator il = mt.GetILGenerator();
    // Unmanaged関数呼び出し
    il.Emit(OpCodes.Ldarg_2);
    il.Emit(OpCodes.Ldarg_3);
    il.Emit(OpCodes.Ldarg_1);
    il.EmitCalli(OpCodes.Calli, CallingConvention.StdCall, 
      typeof(int), new Type {typeof(int), typeof(int)});
    il.Emit(OpCodes.Ret);
    MethodInfo mi = typeof(ICalc).GetMethod("EmitAdd");
    // オーバーライド
    tb.DefineMethodOverride(mt, mi);

    return tb.CreateType().GetConstructor(Type.EmptyTypes).Invoke(null) as ICalc;
  }

  static App() {
  }

  public static int AddStatic(int x, int y) {
    return x + y;
  }

  public int AddMember(int x, int y) {
    return x + y;
  }

  public virtual int AddVirtual(int x, int y) {
    return x + y;
  }

  public static void Main() {
    App app = new App();

    // DllImport
    DateTime t;
    t = DateTime.Now;
    for (int i = 0; i < LOOP; ++i)
      AddDLL(i, i);
    Console.WriteLine("DLL    -" + (DateTime.Now - t));

    // Static Method
    t = DateTime.Now;
    for (int i = 0; i < LOOP; ++i)
      AddStatic(i, i);
    Console.WriteLine("Static -" + (DateTime.Now - t));

    // NonVirtual Method
    t = DateTime.Now;
    for (int i = 0; i < LOOP; ++i)
      app.AddMember(i, i);
    Console.WriteLine("Member -" + (DateTime.Now - t));

    // Virtual Method
    t = DateTime.Now;
    for (int i = 0; i < LOOP; ++i)
      app.AddVirtual(i, i);
    Console.WriteLine("Virtual-" + (DateTime.Now - t));

    // LoadLibraryを使う
    IntPtr hModule = LoadLibrary("Calc.dll");
    IntPtr hFunc = GetProcAddress(hModule, "_Add@8");

    // Native Method via IL
    t = DateTime.Now;
    for (int i = 0; i < LOOP; ++i) 
      CalcIL.AddIL(hFunc, i, i);
    Console.WriteLine("IL     -" + (DateTime.Now - t));

    ICalc calc = GetEmitClass();  
    // Native Method via Emit
    t = DateTime.Now;
    for (int i = 0; i < LOOP; ++i) 
      calc.EmitAdd(hFunc, i, i);
    Console.WriteLine("Emit   -" + (DateTime.Now - t));
    FreeLibrary(hModule);
  }
}

/* 実行結果
DLL    -00:00:12.7187500
Static -00:00:00.1093750
Member -00:00:00.0937500
Virtual-00:00:00.8593750
IL     -00:00:06.4687500
Emit   -00:00:06.6718750
*/

P/Invoke、結構遅いぞ!ILを使うと半分くらいに出来ますが、ここまで頑張らないと駄目なんでしょうか?とは言え、1億回ループの結果なのでよほどのことが無ければ実用上は問題ない気もしますね。(^^;

あと、Reflection.Emitで呼び出せないのが謎です。技術的に無理なのでしょうか・・・
(追記) インスタンスメソッドということを忘れてました。thisの分が計算に入ってない・・・。この前作ったのはグローバル関数だったので勘違いしてしまいました。と言い訳してみるテスト。