从逻辑上讲,我总是认为C#编译器可以使用非常简短的方法,因此只需手动输入方法中的代码就不会显示任何开销......
直到今天 - 当我尝试对各种方法进行基准测试并手动内联代码时。事实证明,对于我来说,即使最简单的代码也会显示一个方法调用开销,与其手动内联对应相比 实际上,我找不到关于内联任何方法的线索 - 所以我进行了一个简单的测试。
使用的系统:
所有测试都是没有调试并使用发布配置(优化代码)执行。
以下是我用来进行基准测试的代码:
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次运行的结果:
[2] 切换到方法调用:
static int Increment(int a)
{
return a + 1;
}
...
// in Main
for (int i = 0; i < iterations; i++)
{
s = Increment(s);
}
5次运行的结果:
哎哟!显然,该方法的开销很大。
我尝试使用反射,并在MethodBase.GetCurrentMethod().Name
方法中打印Increment
;确实打印Increment
- 意味着该方法没有内联。
接下来,我尝试将[MethodImpl(MethodImplOptions.NoInlining)]
属性添加到方法 - ,但基准时间保持完全相同。
在调试模式下,将Optimize Code设置为false,第一个测试稍慢,而第二个测试慢两倍;而 NoInlining 属性再次影响性能。
我在这里做错了什么,即使这样一个简单的方法没有开销也无法工作?为什么会这样?
当然,这不是预期的行为 - 或者是它?
注意:Java中的类似测试显示此类方法调用没有开销。 (使用Eclipse + JDK 1.7,Java似乎也 A LOT 更快。)
答案 0 :(得分:10)
如果从Visual Studio中运行程序,请确保使用 Start Without Debugging 命令;否则,可能会禁用内联等一些优化。
如果我首先使用 Start Without Debugging 命令来运行程序,然后附加调试器并查看for
循环的x86反汇编,无论循环直接递增s
还是调用Increment
,我都会得到同样的结果;也就是说,方法调用是内联的:
00000049 xor eax,eax
0000004b inc ebx
0000004c inc eax
0000004d cmp eax,0EE6B280h
00000052 jl 0000004B
相反,如果我使用 Start Debugging 命令来运行程序,则不会内联方法调用:
00000060 xor edx,edx
00000062 mov dword ptr [ebp-0Ch],edx
00000065 nop
00000066 jmp 0000007D
00000068 mov ecx,dword ptr [ebp-8]
0000006b call dword ptr ds:[00801F50h]
00000071 mov dword ptr [ebp-10h],eax
00000074 mov eax,dword ptr [ebp-10h]
00000077 mov dword ptr [ebp-8],eax
0000007a inc dword ptr [ebp-0Ch]
0000007d cmp dword ptr [ebp-0Ch],0EE6B280h
00000084 jl 00000068