在.NET中,应该说委托是非常平凡的一个概念,常常用于事件机制的实现,动态算法的定义等方面。它很常见,很简单,似乎没什么值得研究的。
但是,今天我对委托有了新发现,发现平凡的“委托”也可以有它不凡的一面。
我们知道,委托是一种引用方法的类型,可以通过命名方法创建委托的实例,也可以定义匿名的委托实例,例如:
1 // ComputeHandler 是一个执行算术计算的委托;
2 public delegate int ComputeHandler(int a1, int a2, int a3);
3
4 //定义匿名委托实现计算;
5 ComputeHandler anonymousHandler = delegate(int v1, int v2, int v3)
6 {
7 return v1 * v2 * v3;
8 };
9
10 //定义命名委托实现计算;
11 ComputeHandler namedHandler = new ComputeHandler(Compute);
12
13
14
15 //命名方法
16 private static int Compute(int v1, int v2, int v3)
17 {
18 return v1 * v2 * v3;
19 }
但以上的这些都不新鲜,接下来的才是新鲜玩意:通过动态方法创建委托实例。
//使用动态方法创建委托实例;
private static ComputeHandler GetDynamicMethod()
{
DynamicMethod mth = new DynamicMethod("_", typeof(int),
new Type[] { typeof(int), typeof(int), typeof(int) });
ILGenerator il = mth.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Mul);
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Mul);
il.Emit(OpCodes.Ret);
return (ComputeHandler) mth.CreateDelegate(typeof(ComputeHandler));
}
也许你会说,这只是定义的方式不同罢了,特别之处在哪儿呢?我告诉你,特别之处在于性能。
在这里,实现对 v1、v2、v3 三个整数进行相乘运算的调用有 4 种方式:1、匿名委托调用;2、命名委托调用;3、动态方法调用;4、直接对编译方法调用;
下面的测试用例用于比较这 4 种方式的性能表现:
int v1 = 2;
int v2 = 3;
int v3 = 8;
int testCount = 5;
int computeCount = 10000 * 10000;
int res = 0;
Console.WriteLine(//定义匿名委托实现计算;"匿名委托执行……");
ComputeHandler anonymousHandler = GetAnonymousDelagate();
for (int t = 0; t < testCount; t++)
{
DateTime start = DateTime.Now;
for (int i = 0; i < computeCount; i++)
{
res = anonymousHandler(v1, v2, v3);
}
DateTime end = DateTime.Now;
Console.Write("\t{0}\t", (end - start).TotalMilliseconds.ToString("0.00"));
}
Console.WriteLine("\r\n 计算结果:{0} ;", res);
Console.WriteLine();
Console.WriteLine("命名委托执行……");
Console.Write(" 耗时(ms)");
//定义命名委托实现计算;
ComputeHandler namedHandler = GetAnonymousDelagate();
for (int t = 0; t < testCount; t++)
{
DateTime start = DateTime.Now;
for (int i = 0; i < computeCount; i++)
{
res = namedHandler(v1, v2, v3);
}
DateTime end = DateTime.Now;
Console.Write("\t{0}\t", (end - start).TotalMilliseconds.ToString("0.00"));
}
Console.WriteLine("\r\n 计算结果:{0} ;", res);
Console.WriteLine();
Console.WriteLine("动态方法执行……");
Console.Write(" 耗时(ms)");
//定义动态方法实现计算;
ComputeHandler dynamicHandler = GetDynamicMethod();
for (int t = 0; t < testCount; t++)
{
DateTime start = DateTime.Now;
for (int i = 0; i < computeCount; i++)
{
res = dynamicHandler(v1, v2, v3);
}
DateTime end = DateTime.Now;
Console.Write("\t{0}\t", (end - start).TotalMilliseconds.ToString("0.00"));
}
Console.WriteLine("\r\n 计算结果:{0} ;", res);
Console.WriteLine();
Console.WriteLine("编译方法执行……");
Console.Write(" 耗时(ms)");
for (int t = 0; t < testCount; t++)
{
DateTime start = DateTime.Now;
for (int i = 0; i < computeCount; i++)
{
res = Compute(v1, v2, v3);
}
DateTime end = DateTime.Now;
Console.Write("\t{0}\t", (end - start).TotalMilliseconds.ToString("0.00"));
}
Console.WriteLine("\r\n 计算结果:{0} ;", res);
Console.Write(" 耗时(ms)");
测试结果如下:
1、匿名委托执行:2187.50 - 2218.75 ms ;
2、命名委托执行:2156.25 - 2187.50 ms ;
3、动态方法执行:1062.50 - 1093.75 ms ;
4、编译方法执行:1812.50 - 1843.75 ms ;
测试结果显示,此测试中动态方法的性能最高,其耗时只有其它方式的 50% 左右,比编译方法还快得多。
最差的是匿名委托,命名委托与之相差不大,编译方法比匿名方法提升约 16% 。
这一结果为我们设计高性能的 .NET 程序指出了一种方法:对于核心的、被大量频繁调用的算法通过 IL 仔细定义动态方法有可能获得非常可观的性能提升。
说明:由于本文所述案例的测试场景是在调试环境下,当时并未考虑到编译器优化的因素。当考虑编译器优化的因素后,本文所用的测试案例对于“使用动态方法提升性能”这一论点就难以支持,将在下篇文章中对此进行阐述。