4

その場で動的メソッドを作成する最良の方法は何ですか?VSでコンパイルされた場合でも同じように効率的ですか?

電卓を作成したいとします。ユーザー入力式は、A + B / C * 0.5 と言います。

私が望むのは、A、B、C を double パラメーターとして受け入れ、double を返す Func のようなものを作成できるようにすることです。

パラメータの型と戻り値の型は常に double です。パラメータの数は可変ですが、少なくとも 1 つです。

これらの数式は頻繁に変更/追加できます。数式が「コンパイル」されると、1 秒あたり 1000 回呼び出すことができる低レイテンシ コードの一部になります。

それを構築するためのシンプルで信頼できる方法を見つける必要がありますが、静的に構築され最適化されたメソッドの正確なパフォーマンス品質を備えている必要があります

4

4 に答える 4

12

これに関する Microsoft ブログ (動的メソッドの生成) を見つけ、静的メソッド、コンパイル済み式ツリー、および IL インジェクションのパフォーマンスを比較しました。

コードは次のとおりです。

    static void Main(string[] args)
    {
        double acc = 0;

        var il = ILFact();
        il.Invoke(1);

        var et = ETFact();
        et(1);

        Stopwatch sw = new Stopwatch();

        for (int k = 0; k < 10; k++)
        {
            long time1, time2;

            sw.Restart();

            for (int i = 0; i < 30000; i++)
            {
                var result = CSharpFact(i);
                acc += result;
            }

            sw.Stop();

            time1 = sw.ElapsedMilliseconds;

            sw.Restart();

            for (int i = 0; i < 30000; i++)
            {
                double result = il.Invoke(i);
                acc += result;
            }

            sw.Stop();

            time2 = sw.ElapsedMilliseconds;

            sw.Restart();

            for (int i = 0; i < 30000; i++)
            {
                var result = et(i);
                acc += result;
            }

            sw.Stop();

            Console.WriteLine("{0,6} {1,6} {2,6}", time1, time2, sw.ElapsedMilliseconds);
        }

        Console.WriteLine("\n{0}...\n", acc);
        Console.ReadLine();
    }

    static Func<int, int> ILFact()
    {
        var method = new DynamicMethod(
        "factorial", typeof(int),
        new[] { typeof(int) }
        );

        var il = method.GetILGenerator();
        var result = il.DeclareLocal(typeof(int));
        var startWhile = il.DefineLabel();
        var returnResult = il.DefineLabel();

        // result = 1

        il.Emit(OpCodes.Ldc_I4_1);
        il.Emit(OpCodes.Stloc, result);

        // if (value <= 1) branch end

        il.MarkLabel(startWhile);
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldc_I4_1);
        il.Emit(OpCodes.Ble_S, returnResult);

        // result *= (value--)

        il.Emit(OpCodes.Ldloc, result);
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Dup);
        il.Emit(OpCodes.Ldc_I4_1);
        il.Emit(OpCodes.Sub);
        il.Emit(OpCodes.Starg_S, 0);
        il.Emit(OpCodes.Mul);
        il.Emit(OpCodes.Stloc, result);

        // end while

        il.Emit(OpCodes.Br_S, startWhile);

        // return result

        il.MarkLabel(returnResult);
        il.Emit(OpCodes.Ldloc, result);
        il.Emit(OpCodes.Ret);

        return (Func<int, int>)method.CreateDelegate(typeof(Func<int, int>));
    }

    static Func<int, int> ETFact()
    {
        // Creating a parameter expression.
        ParameterExpression value = Expression.Parameter(typeof(int), "value");

        // Creating an expression to hold a local variable. 
        ParameterExpression result = Expression.Parameter(typeof(int), "result");

        // Creating a label to jump to from a loop.
        LabelTarget label = Expression.Label(typeof(int));

        // Creating a method body.
        BlockExpression block = Expression.Block(

        // Adding a local variable.
        new[] { result },

        // Assigning a constant to a local variable: result = 1
        Expression.Assign(result, Expression.Constant(1)),

        // Adding a loop.
        Expression.Loop(

        // Adding a conditional block into the loop.
        Expression.IfThenElse(

        // Condition: value > 1
        Expression.GreaterThan(value, Expression.Constant(1)),

        // If true: result *= value --
        Expression.MultiplyAssign(result,
        Expression.PostDecrementAssign(value)),

        // If false, exit from loop and go to a label.
        Expression.Break(label, result)
        ),

        // Label to jump to.
        label
        )
        );

        // Compile an expression tree and return a delegate.
        return Expression.Lambda<Func<int, int>>(block, value).Compile();
    }

    static int CSharpFact(int value)
    {
        int result = 1;
        while (value > 1)
        {
            result *= value--;
        }

        return result;
    }

これは、i7-920 で行われた 3 つの実行です。ビルド - x64 をリリース

583    542    660
577    578    666
550    558    652
576    575    648
570    574    641
560    554    640
558    551    650
561    551    666
624    638    683
564    581    647

-3778851060...

482    482    557
489    490    580
514    517    606
541    537    626
551    524    641
563    555    631
552    558    644
572    541    652
591    549    652
562    552    639

-3778851060...

482    482    560
507    503    591
525    543    596
555    531    609
553    556    634
540    552    640
579    598    635
607    554    639
588    585    679
547    560    643

-3778851060...

平均: 554 549 634

静的 vs IL - IL は 1% 速い(!) 理由はわかりませんが

静的 vs ET -式ツリーより静的 14% 高速


編集 (2014 年 2 月) : .NET 4.5 以上の高速な CPU で上記のコードを (非常にわずかな変更を加えて) 実行したところ、新しい結果セットが得られました: Method / ET - 9%、Method / IL - 4%

したがって、以前の結果はもはや有効ではありません -静的メソッド呼び出しは常に高速です..

*それが新しいハードウェア ( i7-3820 ) なのか新しい .NET なのか、それとも古いテストで何か間違ったことをしたのかわからない.*

もう 1 つの興味深い結果は、32 ビットでは、まったく同じコードが 3 の間でまったく違いがないことを示しています。

Method IL     ET    
--------------------
368    382    399
367    382    399
367    382    399
367    382    400
367    383    400
367    382    399
367    383    399
367    382    399
367    382    399
367    383    400
367    382    399
367    382    399
367    382    399
367    382    399
367    383    400
367    382    400
367    383    399
367    383    400
367    382    399
367    382    400

-7557702120...

--------------------
367.05 382.30 399.35
于 2012-05-23T13:19:10.237 に答える
4

Compile()式ツリーを作成する必要があります。

于 2012-05-20T13:21:57.130 に答える
1

コンパイルされたコードを使用する動的計算機の例を次に示します。ソースが利用可能です。

http://www.c-sharpcorner.com/UploadFile/mgold/CodeDomCalculator08082005003253AM/CodeDomCalculator.aspx

于 2012-05-20T13:37:40.897 に答える
0

それは使用法と最適化に依存します。テストが完璧でない場合、ベンチマークは嘘をつく可能性があります。それを正しく行うには、ルールを知る必要があります。

最初のルール

  • 静的メソッドはコンパイル時に最適化でき、インライン化できます。
  • IL が発行するメソッド (DynamicMethod) は、必要に応じて最適化できるため、純粋な IL の観点から高速になる可能性があります (標準のオプティマイザーよりも優れている場合)。
  • Expression Tree は DynamicMethod に基づいていますが、手動で最適化することはできません。

2つ目のルール

  • パフォーマンスは、呼び出しメカニズムとメソッドの純粋な実行によって表されます。
  • デリゲートを使用したメソッドの呼び出しはオーバーヘッドを意味します
  • インライン抑制呼び出しメカニズム。
  • DynamicMethod は、他の DynamicMethod 内でのみインライン化できます
  • DynamicMethod と Expression Tree は、ほとんどの場合、デリゲートを使用して呼び出されます。
  • インスタンス メソッドへのデリゲートは、静的メソッドへのデリゲートよりも高速です。

心に留めておいてください:

  • 静的メソッドは、本体が小さいメソッドの場合、多くの場合高速です。
  • 呼び出しメカニズムが落とし穴でない場合、DynamicMethod はより高速になる可能性があります。
  • Expression Tree は DynamicMethod よりも高速になることはありませんが、"表現" の方法によっては (まれに) 静的メソッドよりも高速になることがあります。

結論 :

パフォーマンスはコンテキストに依存します。可能であれば、静的メソッドを使用し続けます。オプティマイザが変更されると、パフォーマンスが変わる可能性があります。

于 2016-12-19T14:14:55.933 に答える