Вызов делегата. DynamicInvoke против Func <object>() - PullRequest
0 голосов
/ 23 апреля 2019

Я тестировал некоторый код, который создает экземпляр типа, и этот результат показался мне странным:

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 нс
  • Функц: 4,710 нс

Кто-нибудь знает, почему такая огромная разница?

Завершенные тесты:

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

}

1 Ответ

3 голосов
/ 24 апреля 2019

Разница в производительности вызвана разной производительностью Invoke() (быстро) и DynamicInvoke() (медленно). Взглянув на сгенерированный IL прямого вызова делегата с типом Func<object>, вы увидите, что результирующий 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();
    }
}

Результаты на моем ПК дома с использованием сборки выпуска без подключенного отладчика (как уже упоминалось выше, я знаю, что этот простой тест может быть не очень точным, но он демонстрирует огромную разницу в производительности)

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