1

I've been benchmarking some code that creates an instance of a type and this result seemed weird to me:

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

vs

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

Using BenchmarDotNet gives (Mean, Core):

  • Delegate: 501.790 ns
  • Func: 4.710 ns

Anyone knows why the difference is so huge?

Complete benchmarks:

[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();
    }

}

The performance difference is caused by the different performance of Invoke() (fast) and DynamicInvoke() (slow). When taking a look at the generated IL of a direct call to a Func<object> typed delegate you can see that the resulting IL will actually call the Invoke() method:

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

The above compiles to IL code looking something like this (in a debug build):

.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

And the Invoke() method is much faster than the DynamicInvoke() method, since it basically doesn't need to resolve the type of the delegate (as it is already known). The following answer to another question explains the difference of Invoke() and DynamicInvoke() in a bit more detail: https://stackoverflow.com/a/12858434/6122062

The following very simplified and probably not very accurate test shows a huge difference in perfomance. As you can see there I'm even using the same delegate, just calling it in different ways:

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();
    }
}

Results on my PC at home using a release build, without an attached debugger (as mentioned above I know this simple test might not be very accuarate, but it demonstates the huge difference in performance)

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

1 回答 1

4

性能差异是由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()https ://stackoverflow.com/a/12858434/6122062DynamicInvoke()

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

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
于 2019-04-23T21:46:40.207 回答