【问题标题】:.NET Dynamic method. Best performance.NET 动态方法。最棒的表演
【发布时间】:2012-05-27 06:36:03
【问题描述】:

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

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

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

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

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

我需要找到简单可靠的方法来构建它,但它必须具有与静态构建和优化方法完全相同的性能品质

【问题讨论】:

  • 您是否已经有了获取运营商的机制?
  • 没有。我只是在计划...

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


【解决方案1】:

我在此 (Generating Dynamic Methods ) 上找到了 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%,方法 / 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

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 你能给我一个大概的想法吗?
    • @Bobb:解析字符串后,使用Expression.* 方法构建表达式树。 msdn.microsoft.com/en-us/library/bb397951.aspx
    • 只要我理解,表达式需要先声明。即`ExpressionTreeBuilder b = new ExpressionTreeBuilder();表达式> e = b.GetExpressionTreeFromString("a + b - c"); var d = e.Compile(); var result = d(1, 2, 3);` @@@ question - 你如何处理可变数量的参数?
    【解决方案3】:

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

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

    【讨论】:

    • Chris 使用 CSharpCodeProvider 你将在几天内结束数百个 dll 挂起......只要我理解这一点,它会在每次调用时编译一个新的 dll。对吗?
    • @Bobb - 我还没有完全测试,抱歉。只是把它作为资源扔出去。
    • 没关系。我知道 CSharpCodeProvider,如果我找不到其他方法,我会尝试你的代码。只是我想找到更易于管理的东西。谢谢
    【解决方案4】:

    这取决于使用和优化。 如果您的测试不完美,基准可能会撒谎。 您必须了解规则才能使其正确。

    第一条规则

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

    第二条规则

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

    记住:

    • 静态方法通常对于小主体的方法更快。
    • 如果调用机制不是陷阱,DynamicMethod 可以更快。
    • 表达式树不能比 DynamicMethod 更快,但有时(很少)会比静态方法更快,具体取决于您“表达”的方式。

    结论:

    性能取决于上下文。 如果可能,请继续使用静态方法。 如果优化器发生变化,性能可能会发生变化。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多