直接调用、委托调用和反射方法调用性能基准测试






4.61/5 (13投票s)
2003年1月6日
2分钟阅读

144787

446
这个控制台模式小程序演示了使用反射调用方法时带来的显著性能损失。
引言
本文的灵感来源于
这两篇文章都激发了我对 C# 中提供的三种函数调用形式进行一些基准测试
- 直接调用
- 通过委托调用
- 通过反射调用
由于我正处于“应用程序自动化层”的原型设计阶段(应用程序自动化层:介绍和设计 [^] 和 应用程序自动化层 - 引导加载程序和组件管理器的设计与实现 [^]),该层目前严重依赖于通过反射进行方法调用,因此我认为应该采纳这两篇文章的建议,并查看当前实现的性能。
结果令人惊讶。
C# 基准测试
难道我没有在 CP 上读到 Microsoft 许可协议明确声明“不得对 .NET 进行基准测试!”吗? 好吧,这是我违反的另一个戒律。
我决定编写一个非常简单的基准测试程序,目的不是为了收集三种函数调用类型之间的细微差异,而是为了确定是否存在任何大的差异。 基准测试程序比较了
类型 | 直接 | 委托 | Reflection(反射) |
---|---|---|---|
静态,无参数 | - | - | - |
静态,带参数 | - | - | - |
实例,无参数 | - | - | - |
实例,带参数 | - | - | - |
结果如下
您会注意到反射比直接调用慢大约 50 倍。 这意味着我将不得不认真重新考虑我在 AAL 中的实现!
一些代码
该程序对十二种不同类型的调用进行基准测试。 它们看起来基本上都是这样
public static void StaticDirectCallWithoutParams()
{
++count;
}
仅仅包含一个计数器递增。
在程序开始时,委托和反射方法以及一些嵌入式常量被初始化,这些常量控制测试的运行次数,以及我们花费多少毫秒来调用被测函数
CallBenchmark cb=new CallBenchmark();
SNPCall snpCall=new SNPCall(StaticDelegateWithoutParams);
SPCall spCall=new SPCall(StaticDelegateWithParams);
INPCall inpCall=new INPCall(cb.InstanceDelegateWithoutParams);
IPCall ipCall=new IPCall(cb.InstanceDelegateWithParams);
MethodInfo snpMI=GetMethodInfo
("CallBenchmark.exe/CallBenchmark.CallBenchmark/StaticInvokeWithoutParams");
MethodInfo spMI=GetMethodInfo
("CallBenchmark.exe/CallBenchmark.CallBenchmark/StaticInvokeWithParams");
MethodInfo inpMI=GetMethodInfo
("CallBenchmark.exe/CallBenchmark.CallBenchmark/InstanceInvokeWithoutParams");
MethodInfo ipMI=GetMethodInfo
("CallBenchmark.exe/CallBenchmark.CallBenchmark/InstanceInvokeWithParams");
int sampleSize=20; // # of samples
int timerInterval=200; // in ms
设置一个计时器来停止测试
Timer timer=new Timer(timerInterval);
timer.AutoReset=false;
timer.Stop();
timer.Elapsed+=new ElapsedEventHandler(OnTimerEvent);
...
static void OnTimerEvent(object src, ElapsedEventArgs e)
{
done=true;
}
每个测试看起来都像这样
++n;
count=0;
done=false;
timer.Start();
while (!done)
{
StaticDirectCallWithParams(1, 2, 3);
}
benchmarks[n]+=count;
火箭科学,不是吗?
结论
这个小测试清楚地表明了使用反射调用方法所带来的性能损失。 即使像 CPU 缓存这样的考虑因素也无法解释这种显着差异(我想!)。 显然,我需要重新考虑我的架构实现。