从逻辑上讲,我一直认为非常短且简单的方法会被 C# 编译器内联,因此与简单地手动在方法中键入代码相比,不会显示任何开销......
直到今天 - 当我尝试对各种方法和手动内联代码进行基准测试时。事实证明(对我来说)即使是最简单的代码也会显示出与其手动内联对应的方法调用开销相比。
实际上,我找不到任何关于内联方法的线索-所以我进行了一个简单的测试。
使用的系统:
- 英特尔 C2D E7200(双核)2.53GHz
- 4GB DDR2
- 视窗 7 64 位
- .NET 4.0
- Visual Studio 2010 终极版
所有测试都是在没有 Debug和使用Release Configuration (Optimize Code)的情况下执行的。
这是我用来基准测试的代码:
static void Main()
{
const int iterations = 250000000; // 250 million iterations
Thread.Sleep(1000); // sleep for one second
var sw = new Stopwatch();
int s = 0;
sw.Start();
for (int i = 0; i < iterations; i++)
{
// incrementing s by 1 in various ways
}
sw.Stop();
Console.WriteLine("Time: {0}ms", sw.ElapsedMilliseconds);
}
[1]首先,我简单地对一个简单的增量命令进行了基准测试:
// in Main
for (int i = 0; i < iterations; i++)
{
s = s + 1;
}
5 次运行的结果:
- 867毫秒
- 877 毫秒
- 868毫秒
- 865毫秒
- 870毫秒
[2]切换到方法调用:
static int Increment(int a)
{
return a + 1;
}
...
// in Main
for (int i = 0; i < iterations; i++)
{
s = Increment(s);
}
5 次运行的结果:
- 2161毫秒
- 2159 毫秒
- 2194 毫秒
- 2177毫秒
- 2163毫秒
哎哟! 显然,该方法存在开销。
我尝试使用反射,并MethodBase.GetCurrentMethod().Name
从Increment
方法中打印;它确实在打印Increment
- 这意味着该方法没有内联。
接下来我尝试将[MethodImpl(MethodImplOptions.NoInlining)]
属性添加到方法中 -但基准时间保持完全相同。
在 Debug 模式下,Optimize Code 设置为 false,第一个测试稍慢,而第二个测试大约慢两倍;再次,NoInlining属性不会影响性能。
我在这里做错了什么,以至于即使如此简单的方法也无法在没有开销的情况下工作?为什么会这样?
这肯定不是预期的行为 - 或者是吗?
注意:Java 中的类似测试没有显示此类方法调用的开销。(使用 Eclipse + JDK 1.7,Java 在这方面似乎也快了很多。)