创建委托以大幅度提高反射调用的性能

前言

反射这种东西,大家都知道伤性能,但有的时候就是不得不用反射。那咋办呢?

那就是为反射得到的方法创建一个委托!

这种方式能够提高近乎直接调用方法本身的性能。
(当然Emit也能够帮助我们显著提升性能,不过直接得到可以调用的委托岂不是更加方便

咱们先创建一个类,用来做测试

1
2
3
4
5
6
7
private class Foo
{
public int Test(int i)
{
return i;
}
}

先创建一个实例

1
2
// 调用的目标实例。
var instance = new Foo();

一般来说,我们调用方法直接可以这样写:

1
var result = instance.Test(5);

这样写无疑是最不损耗性能的写法,所以是直接调用。

既然博文标题都说了是创建反射的委托,那咱们就先直接写个和Test方法功能一样的纯委托,用作性能对比测试

1
2
// 跟被测方法功能一样的纯委托。
Func<int, int> pureFunc = value => value;

下面是反射的使用,先写个通过反射拿到方法

1
2
// 使用反射找到的方法。
var method = typeof(Foo).GetMethod(nameof(Foo.Test), new[] { typeof(int) });

拿到方法后就是调用,如果是直接调用,那么就是

1
var result = method.Invoke(instance, new object[] { 5 });

当然,这个是先用GetMethod拿到方法缓存后的调用。如果是直接使用反射调用的话

1
var result = typeof(Foo).GetMethod(nameof(Foo.Test), new[] { typeof(int) })?.Invoke(instance, new object[] { 5 });

下面就是本文的重点

那就是将反射找到的方法创建一个委托

如何实现?

实现的关键就在于 MethodInfo.CreateDelegate 方法。这是 .NET Standard 中就有的方法,这意味着 .NET Framework 和 .NET Core 中都可以使用。

此方法有两个重载:

1.要求传入一个类型,而这个类型就是应该转成的委托的类型
2.要求传入一个类型和一个实例,一样的,类型是应该转成的委托的类型

他们的区别在于前者创建出来的委托是直接调用那个实例方法本身,后者则更原始一些,真正调用的时候还需要传入一个实例对象。

1
2
3
// 将反射找到的方法创建一个委托。
var func = (Func<int, int>)method.CreateDelegate(typeof(Func<int, int>), instance);
var func2 = (Func<Foo, int, int>)method.CreateDelegate(typeof(Func<Foo,int, int>));

前者得到的委托相当于 int Test(int i) 方法,后者得到的委托相当于 int Test(Foo instance, int i) 方法。(在 IL 里实例的方法其实都是后者,而前者更像 C# 中的代码,容易理解。)

以下是性能测试代码,可以更好的理解本文:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
using System;
using System.Diagnostics;
using System.Reflection;
using System.Diagnostics.Contracts;

namespace ConsoleApp10
{
public class Program
{
static void Main(string[] args)
{
// 调用的目标实例。
var instance = new Foo();

// 使用反射找到的方法。
var method = typeof(Foo).GetMethod(nameof(Foo.Test), new[] { typeof(int) });

// 将反射找到的方法创建一个委托。
var func = InstanceMethodBuilder<int, int>.CreateInstanceMethod(instance, method);
//var func = (Func<int, int>)method.CreateDelegate(typeof(Func<int, int>), instance);
//var func2 = (Func<Foo, int, int>)method.CreateDelegate(typeof(Func<Foo,int, int>));

// 跟被测方法功能一样的纯委托。
Func<int, int> pureFunc = value => value;

// 测试次数。
var count = 10000000;

// 直接调用。
var watch = new Stopwatch();
watch.Start();
for (var i = 0; i < count; i++)
{
var result = instance.Test(5);
}

watch.Stop();
Console.WriteLine($"{watch.Elapsed} - {count} 次 - 直接调用");

// 使用同样功能的 Func 调用。
watch.Restart();
for (var i = 0; i < count; i++)
{
var result = pureFunc(5);
}

watch.Stop();
Console.WriteLine($"{watch.Elapsed} - {count} 次 - 使用同样功能的 Func 调用");

// 使用反射创建出来的委托调用。
watch.Restart();
for (var i = 0; i < count; i++)
{
var result = func(5);
//var result = func2(instance,5);
}

watch.Stop();
Console.WriteLine($"{watch.Elapsed} - {count} 次 - 使用反射创建出来的委托调用");

// 使用反射得到的方法缓存调用。
watch.Restart();
for (var i = 0; i < count; i++)
{
var result = method.Invoke(instance, new object[] { 5 });
}

watch.Stop();
Console.WriteLine($"{watch.Elapsed} - {count} 次 - 使用反射得到的方法缓存调用");

// 直接使用反射调用。
watch.Restart();
for (var i = 0; i < count; i++)
{
var result = typeof(Foo).GetMethod(nameof(Foo.Test), new[] { typeof(int) })
?.Invoke(instance, new object[] { 5 });
}

watch.Stop();
Console.WriteLine($"{watch.Elapsed} - {count} 次 - 直接使用反射调用");

Console.ReadLine();
}

private class Foo
{
public int Test(int i)
{
return i;
}
}

public static class InstanceMethodBuilder<T, TReturnValue>
{
/// <summary>
/// 调用时就像 var result = func(t)。
/// </summary>
[Pure]
public static Func<T, TReturnValue> CreateInstanceMethod<TInstanceType>(TInstanceType instance, MethodInfo method)
{
if (instance == null) throw new ArgumentNullException(nameof(instance));
if (method == null) throw new ArgumentNullException(nameof(method));

return (Func<T, TReturnValue>)method.CreateDelegate(typeof(Func<T, TReturnValue>), instance);
}

/// <summary>
/// 调用时就像 var result = func(this, t)。
/// </summary>
[Pure]
public static Func<TInstanceType, T, TReturnValue> CreateMethod<TInstanceType>(MethodInfo method)
{
if (method == null)
throw new ArgumentNullException(nameof(method));

return (Func<TInstanceType, T, TReturnValue>)method.CreateDelegate(typeof(Func<TInstanceType, T, TReturnValue>));
}
}
}
}

性能对比:

解释一下这五行数据的含义:

1.直接调用(应该没有什么比直接调用函数本身更有性能优势的吧)

2.做一个跟直接调用的方法功能一模一样的委托(目的是看看调用委托相比调用方法本身是否有性能损失,从数据上看,损失非常小)

3.本文重点 将反射出来的方法创建一个委托,然后调用这个委托(看看吧,性能跟直接调差别也不大嘛)

4.先反射得到方法,然后一直调用这个方法(终于可以看出来反射本身还是挺伤性能的了,50 多倍的性能损失啊)

5.缓存都不用,从头开始反射然后调用得到的方法(100 多倍的性能损失了)

封装:

单独使用 CreateDelegate 方法可能每次都需要尝试第一个参数到底应该传入些什么,于是我将其封装成了泛型版本,增加易用性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static class InstanceMethodBuilder<T, TReturnValue>
{
/// <summary>
/// 调用时就像 var result = func(t)。
/// </summary>
[Pure]
public static Func<T, TReturnValue> CreateInstanceMethod<TInstanceType>(TInstanceType instance, MethodInfo method)
{
if (instance == null) throw new ArgumentNullException(nameof(instance));
if (method == null) throw new ArgumentNullException(nameof(method));

return (Func<T, TReturnValue>)method.CreateDelegate(typeof(Func<T, TReturnValue>), instance);
}

/// <summary>
/// 调用时就像 var result = func(this, t)。
/// </summary>
[Pure]
public static Func<TInstanceType, T, TReturnValue> CreateMethod<TInstanceType>(MethodInfo method)
{
if (method == null)
throw new ArgumentNullException(nameof(method));

return (Func<TInstanceType, T, TReturnValue>)method.CreateDelegate(typeof(Func<TInstanceType, T, TReturnValue>));
}
}

当然,这个泛型封装也是有问题的,也就是创建的委托只能是一个参数。没有实现像Action<T1,T2,T3,Tn>这样的多参数委托

不过多参数版本可以使用泛型类型生成器生成。

不过这个日后再说,之后有时间再给大家写一个泛型生成器