.NET动态方法。最棒的表演

时间:2012-05-20 13:20:11

标签: c# .net optimization dynamic .net-4.0

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

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

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

参数类型和返回类型始终为double。参数数量是可变的,但至少有一个。

这些公式可以经常更改/添加。一旦公式'编译',它将成为低延迟代码的一部分,可以被称为1000次/秒。

我需要找到简单可靠的方法来构建它,但必须具有静态构建和优化方法的精确性能

4 个答案:

答案 0 :(得分:11)

我已经找到了微博(Generating Dynamic Methods )并比较了静态方法,编译表达式树和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%(!)不知道为什么

静态与ET - 静态比表达式树快14%


EDIT(2014年2月):我刚刚在.NET 4.5和更快的CPU上运行了上面的代码(稍加修改)并获得了新的结​​果集:方法/ ET - 9%,方法/ IL - 4%

因此以前的结果不再有效 - 静态方法调用总是更快 ..

*不确定它是新硬件( i7-3820 )还是新的.NET,或者我在旧测试中做错了。*

另一个有趣的结果是,在 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

答案 1 :(得分:4)

您应该创建并Compile()表达式树。

答案 2 :(得分:1)

这是使用编译代码的动态计算器的示例。来源可用。

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

答案 3 :(得分:0)

这取决于使用和优化。 如果您的测试不完美,基准可能会存在。 你必须知道正确的规则。

第一条规则

  • 静态方法可以在编译时进行优化,也可以内联。
  • IL发射方法(DynamicMethod)在纯IL方面可以更快,因为您可以根据需要进行优化(如果您能够比标准优化器做得更好)
  • 表达式树基于DynamicMethod,但您无法手动优化它。

第二条规则

  • 性能由调用机制和方法的纯执行来表示。
  • 使用委托的调用方法意味着开销
  • 内联抑制呼叫机制。
  • DynamicMethod只能在其他DynamicMethod中内联
  • DynamicMethod和Expression Tree大部分时间都是使用委托调用的。
  • 委托实例方法比委托静态方法更快。

请记住:

    对于身体较小的方法,
  • 静态方法通常更快。
  • 如果呼叫机制不是陷阱,DynamicMethod会更快。
  • 表达式树不能比DynamicMethod更快,但有时(很少)比静态方法更快,这取决于你如何“表达它”。

结论:

表现取决于背景。 如果可能的话,继续使用静态方法。 如果优化器发生变化,性能可能会发生变化。