優化委託的 `DynamicInvoke`

WeihanLi發表於2020-04-19

優化委託的 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 });
    }
}

測試結果如下:

由上面的結果,我們可以看出來,直接呼叫方法的效能雖然還是比 Invoke 慢上好多,但是相比 DynamicInvoke 已經優化 70% 左右,對於有裝箱操作的效能會稍差一些,比 DynamicInvoke 優化可達 44% 左右。

Reference