优化委托的 `DynamicInvoke`

优化委托的 DynamicInvoke

Intro

委托方法里有一个 DynamicInvoke 的方法,可以在不清楚委托实际类型的情况下执行委托方法,但是用 DynamicInvoke 去执行的话会比直接用 Invoke 的方法会慢上很多,差了两个数量级,所以在知道委托类型的情况下尽可能使用 Invoke 执行,但有时候我们并不知道委托的实际类型,比如在很多类库项目中可能并不是强类型的委托

优化方法

优化方法,直接执行委托的对应的方法,DynamicInvoke 实际也是调用的对应的方法,我们如果执行调用对应的方法就可以优化

delegate func = (Func<string, string>)str=> "12345";
string paramString = "321";

// Invoke
((Func<string, string>)func).Invoke(paramString);

// DynamicInvoke
func.DynamicInvoke(new object[]{ paramString });

// Method Invoke
func.Method.Invoke(func.Target, new object[]{ paramString });

性能测试

下面做一个性能测试,测试代码如下:

public class DelegateInvokeTest
{
    private readonly Delegate _func, _func1;
    private readonly string parameter;
    private readonly int paramInt;

    public DelegateInvokeTest()
    {
        parameter = "Test";
        paramInt = 1;

        _func = (Func<string, string>)(str => str);
        _func1 = (Func<int, int>)(val => 0);
    }

    [Benchmark(Baseline = true)]
    public object Invoke()
    {
        return ((Func<string, string>)_func).Invoke(parameter);
    }

    [Benchmark]
    public object InvokeBoxing()
    {
        return ((Func<int, int>)_func1).Invoke(paramInt);
    }

    [Benchmark]
    public object DynamicInvoke()
    {
        return _func.DynamicInvoke(parameter);
    }

    [Benchmark]
    public object DynamicInvokeBoxing()
    {
        return _func1.DynamicInvoke(paramInt);
    }

    [Benchmark]
    public object MethodInfoInvoke()
    {
        return _func.Method?.Invoke(_func.Target, new object[] { parameter });
    }

    [Benchmark]
    public object MethodInfoInvokeBoxing()
    {
        return _func1.Method?.Invoke(_func1.Target, new object[] { paramInt });
    }

    [Benchmark]
    public object ReflectInvoke()
    {
        var funcType = typeof(Func<,>).MakeGenericType(typeof(string), typeof(string));
        var method = funcType.GetProperty("Method")?.GetValueGetter()?.Invoke(_func) as MethodInfo;
        var target = funcType.GetProperty("Target")?.GetValueGetter()?.Invoke(_func);
        return method?.Invoke(target, new object[] { parameter });
    }

    [Benchmark]
    public object ReflectInvokeBoxing()
    {
        var funcType = typeof(Func<,>).MakeGenericType(typeof(string), typeof(int));
        var method = funcType.GetProperty("Method")?.GetValueGetter()?.Invoke(_func1) as MethodInfo;
        var target = funcType.GetProperty("Target")?.GetValueGetter()?.Invoke(_func1);
        return method?.Invoke(target, new object[] { paramInt });
    }
}

测试结果如下:


BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.173 (2004/?/20H1)

Intel Core i5-6300U CPU 2.40GHz (Skylake), 1 CPU, 4 logical and 2 physical cores

.NET Core SDK=3.1.201

 [Host]   : .NET Core 3.1.3 (CoreCLR 4.700.20.11803, CoreFX 4.700.20.12001), X64 RyuJIT

 DefaultJob : .NET Core 3.1.3 (CoreCLR 4.700.20.11803, CoreFX 4.700.20.12001), X64 RyuJIT

| Method | Mean | Error | StdDev | Ratio | RatioSD |

|———————– |———–😐———–😐———-😐——-😐——–😐

| Invoke | 2.236 ns | 0.0335 ns | 0.0280 ns | 1.00 | 0.00 |

| InvokeBoxing | 7.557 ns | 0.0659 ns | 0.0585 ns | 3.38 | 0.05 |

| DynamicInvoke | 601.180 ns | 2.6923 ns | 2.3866 ns | 268.83 | 3.50 |

| DynamicInvokeBoxing | 394.025 ns | 3.5004 ns | 3.1030 ns | 176.29 | 2.11 |

| MethodInfoInvoke | 176.440 ns | 0.7207 ns | 0.5627 ns | 79.02 | 0.99 |

| MethodInfoInvokeBoxing | 220.982 ns | 0.4654 ns | 0.3886 ns | 98.86 | 1.29 |

| ReflectInvoke | 917.546 ns | 4.7908 ns | 4.2469 ns | 410.21 | 5.60 |

| ReflectInvokeBoxing | 995.824 ns | 10.9270 ns | 9.6865 ns | 445.48 | 7.16 |

由上面的结果,我们可以看出来,直接调用方法的性能虽然还是比 Invoke 慢上好多,但是相比 DynamicInvoke 已经优化 70% 左右,对于有装箱操作的性能会稍差一些,比 DynamicInvoke 优化可达 44% 左右。

Reference

Tags: