【问题标题】:Calling Delegate.DynamicInvoke vs Func<object>()调用 Delegate.DynamicInvoke 与 Func<object>()
【发布时间】:2019-04-23 19:54:46
【问题描述】:

我一直在对一些创建类型实例的代码进行基准测试,这个结果对我来说似乎很奇怪:

Delegate deleg = Expression.Lambda(Expression.New(_type)).Compile();
// deleg.DynamicInvoke();

Func<object> func = Expression.Lambda<Func<object>>(Expression.New(_type)).Compile();
// func();

使用 BenchmarDotNet 给出(平均,核心):

  • 委托:501.790 ns
  • 函数:4.710 ns

谁知道为什么差别这么大?

完整的基准测试:

[ClrJob(baseline: true), CoreJob, CoreRtJob]
[RPlotExporter, RankColumn]
public class Benchmarks
{

    private Type _type;
    private ConstructorInfo _constructor;
    private Delegate _delegate;
    private Func<object> _func;

    [GlobalSetup]
    public void GlobalSetup()
    {
        _type = typeof(TestClass);
        _constructor = _type.GetConstructor(Type.EmptyTypes);
        _delegate = Expression.Lambda(Expression.New(_type)).Compile();
        _func = Expression.Lambda<Func<object>>(Expression.New(_type)).Compile();
    }

    [Benchmark(Baseline = true)]
    public object Instanciate_Using_New()
    {
        return new TestClass();
    }

    [Benchmark]
    public object Instanciate_Using_Activator()
    {
        return Activator.CreateInstance(_type);
    }

    [Benchmark]
    public object Instanciate_Using_Constructor()
    {
        return _constructor.Invoke(null);
    }

    [Benchmark]
    public object Instanciate_Using_Expression_Delegate()
    {
        return _delegate.DynamicInvoke();
    }

    [Benchmark]
    public object Instanciate_Using_Expression_Func()
    {
        return _func();
    }

}

【问题讨论】:

  • 如果将调用与编译一起测量会怎样?
  • 不,编译是在 GlobalSetup 中完成的;
  • 你应该确保你的问题在他们的目标中是明确的。只有通过阅读代码,您才会发现您的问题不是关于编译性能差异,而是关于调用代码。
  • @LasseVågsætherKarlsen 什么?这个问题怎么不是关于性能的?
  • 这与性能有关,但您正在分析调用已编译的代码,而不是分析编译代码。我正是这个意思。您说您正在对“一些创建类型实例的代码”进行基准测试,然后发布一段代码与另一段代码,但您不是在对这些代码段进行基准测试,而是在使用结果进行基准测试。跨度>

标签: c# benchmarking expression-trees


【解决方案1】:

性能差异是由Invoke()(快)和DynamicInvoke()(慢)的性能不同造成的。查看直接调用Func&lt;object&gt; 类型委托所生成的IL 时,您可以看到生成的IL 实际上会调用Invoke() 方法:

    static void TestInvoke(Func<object> func) {
        func();
    }

上面编译成类似这样的 IL 代码(在调试版本中):

.method private hidebysig static void TestInvoke(class [mscorlib]System.Func`1<object> func) cil managed {
.maxstack 8

IL_0000: nop

IL_0001: ldarg.0      // func
IL_0002: callvirt     instance !0/*object*/ class [mscorlib]System.Func`1<object>::Invoke()
IL_0007: pop

IL_0008: ret

} // end of method Program::TestInvoke

而且Invoke() 方法比DynamicInvoke() 方法快得多,因为它基本上不需要解析委托的类型(众所周知)。以下对另一个问题的回答更详细地解释了Invoke()DynamicInvoke() 的区别: https://stackoverflow.com/a/12858434/6122062

以下非常简化且可能不太准确的测试显示了性能上的巨大差异。如您所见,我什至使用相同的委托,只是以不同的方式调用它:

class Program {
    static void Main(string[] args) {
        var ex = Expression.Lambda<Func<object>>(Expression.New(typeof(object))).Compile();

        Stopwatch timer = Stopwatch.StartNew();
        for (int i = 0; i < 1000000; i++) TestInvoke(ex);
        Console.WriteLine($"Invoke():\t\t{timer.Elapsed.ToString()}");

        timer = Stopwatch.StartNew();
        for (int i = 0; i < 1000000; i++) TestDynamicInvoke(ex);
        Console.WriteLine($"DynamicInvoke():\t{timer.Elapsed.ToString()}");

        Console.ReadKey(true);
    }

    static void TestInvoke(Func<object> func) {
        func();
    }

    static void TestDynamicInvoke(Delegate deleg) {
        deleg.DynamicInvoke();
    }
}

在家中使用发布版本的 PC 上的结果,没有附加调试器(如上所述,我知道这个简单的测试可能不是很准确,但它证明了性能上的巨大差异)

Invoke():               00:00:00.0080935
DynamicInvoke():        00:00:00.8382236

【讨论】:

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