4

动态创建动态方法的最佳方法是什么,但如果它是在 VS 中编译的,它的效率是否相同?

假设我想创建一个计算器。用户输入公式说 A + B / C * 0.5;

我想要的是能够创建类似 Func 的东西,它将接受 A、B、C 作为双参数并返回双精度。

参数类型和返回类型总是双精度。参数的数量是可变的,但至少有一个。

这些公式可以经常更改/添加。一旦公式“编译”,它将成为低延迟代码的一部分,可以调用 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%


EDIT (Feb 2014) : I just ran the code above (with very slight modifications) on .NET 4.5 and faster CPU and got the new sets of results: Method / ET - 9%, Method / IL - 4%

Hence the previous results are not valid any more - the static method call is always faster..

*Not sure whether it is new hardware (i7-3820) or new .NET or perhaps I did something wrong in the old test.*

Another interesting result is that in 32-bit the very same code shows absolutely NO difference between the 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

It depend on usage and optimization. Benchmark can lie if your tests are not perfect. You have to know the rules to make it right.

The first rules

  • static method can be optimized at compile time and can be inlined.
  • IL emitted method (DynamicMethod) can be faster in term of pure IL because you can optimized like you want (if you are able to do it better than standard optimizer)
  • Expression Tree is based on DynamicMethod but you can't optimize it manually.

The second rules

  • Performance is represented by call mechanism and pure execution of method.
  • call method using delegate means overhead
  • inlining suppress call mechanism.
  • DynamicMethod can only be inline in other DynamicMethod
  • DynamicMethod and Expression Tree are most of time called using delegate.
  • Delegate to instance method is faster than delegate to static method.

keep in mind :

  • static method is often faster for methods with small body.
  • DynamicMethod can be faster if call mechanism is not the pitfall.
  • Expression Tree cannot be faster than DynamicMethod but can sometimes (rarely) be faster than static method depending on how you "express it".

conclusion :

performance depend on context. keep using static method if it is possible. Performance can change if optimizer change.

于 2016-12-19T14:14:55.933 回答