「反射」と「式」で実行する方法の性能の違いを比較してみましょう【回転】

18720 ワード

反射を頻繁に使うと性能に影響するため、ASP.NET MVCは、ターゲットアクションメソッドを実行するために式ツリー方式を採用している.具体的には、ASP.NET MVCは、ターゲットアクションメソッドの実行を表す式を構築し、その式を実行可能コードにコンパイルする.コンパイルされた実行可能コードは、同じActionメソッドの実行のためにキャッシュされる委任オブジェクトとして表示されます.2つの(反射と式でコンパイルされた委任オブジェクトを直接利用する)方法の性能の違いを直感的に理解できるように,簡単な例を示した.コンソールアプリケーションでは、Invokeメソッドがテストする必要があるターゲットメソッドであるFoobarタイプを定義しました.簡単に言えば、パラメータは定義されておらず、メソッド自体も具体的な操作を実行する必要はありません.
   1: public class Foobar
   2: {
   3:     public void Invoke(){}
   4: }

具体的なテスト手順は以下の通りです.3つの静的属性Target、Method、Executorは、それぞれ、静的メソッドCreateExecutorメソッドを呼び出すことによって作成される、実行されるターゲットオブジェクト、ターゲットメソッド、および式のコンパイル後に生成される委任オブジェクトを表します.
   1: class Program
   2: {
   3:  
   4:     public static Foobar Target { get; private set; }
   5:     public static MethodInfo Method { get; private set; }
   6:     public static Action<Foobar> Executor { get; private set; }
   7:  
   8:     private static object[] args = new object[0];
   9:  
  10:     private static Action<Foobar> CreateExecutor(MethodInfo method)
  11:     { 
  12:         ParameterExpression target = Expression.Parameter(typeof(Foobar),"target");
  13:         Expression expression = Expression.Call(target, method);
  14:         return Expression.Lambda<Action<Foobar>>(expression, target).Compile();
  15:     }
  16:  
  17:     static Program()
  18:     {
  19:         Target = new Foobar();
  20:         Method = typeof(Foobar).GetMethod("Invoke");
  21:         Executor = CreateExecutor(Method);
  22:     }
  23:  
  24:     static void Main()
  25:     {
  26:         Console.WriteLine("{0,-10}{1,-12}{2}", "Times", "Reflection", "Expression");
  27:         Test(100000);
  28:         Test(1000000);
  29:         Test(10000000);
  30:     }
  31:  
  32:     private static void Test(int times)
  33:     {
  34:         Stopwatch stopwatch = new Stopwatch();
  35:  
  36:         stopwatch.Start();           
  37:         for (int i = 0; i < times; i++)
  38:         {
  39:             Method.Invoke(Target, args);
  40:         }
  41:         long elapsed1 = stopwatch.ElapsedMilliseconds;           
  42:  
  43:         stopwatch.Restart();
  44:         for (int i = 0; i < times; i++)
  45:         {
  46:             Executor(Target);
  47:         }
  48:         long elapsed2 = stopwatch.ElapsedMilliseconds;
  49:  
  50:         Console.WriteLine("{0,-10}{1,-12}{2}", times, elapsed1, elapsed2);
  51:     }
  52: }

テストメソッドTestのパラメータtimesは、ターゲットメソッドを実行した回数を示します.この方法では,MethodInfoオブジェクトのInvokeメソッドを呼び出してターゲットメソッドを反射的に実行し,Executor属性で表される委任オブジェクトを用いてターゲットメソッドを実行し,それらの実行時間(ミリ秒単位)を出力する.プログラムエントリであるMainメソッドでは,Testメソッドを3つ前後して呼び出し,実行対象メソッドの回数をそれぞれ100000(10万),100000(100万),1000000(千万)に設定した.プログラムを実行すると、コンソールで次のような出力結果が得られます.反射方式で直接実行する方法は確かに性能に差がありますが、違いは明らかではありません.多くの人はいつもプログラムの中で反射を使うと性能に大きな影響を与えると思っていますが、実は私から見れば反射自体が性能のボトルネックの元凶ではないことが多いようです.
   1: Times      Reflection     Expression
   2: 100000     34             2
   3: 1000000    273            28
   4: 10000000   2627           284