调用Delegate.DynamicInvoke vs Func()

问题描述 投票:0回答:1

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

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

VS

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

使用BenchmarDotNet给出(Mean,Core):

  • 代表: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();
    }

}
c# benchmarking expression-trees
1个回答
3
投票

性能差异是由Invoke()(快速)和DynamicInvoke()(慢)的不同性能引起的。当查看生成的直接调用Func<object>类型委托的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
© www.soinside.com 2019 - 2024. All rights reserved.