3

从逻辑上讲,我一直认为非常短且简单的方法会被 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 次运行的结果:

  1. 867毫秒
  2. 877 毫秒
  3. 868毫秒
  4. 865毫秒
  5. 870毫秒

[2]切换到方法调用:

static int Increment(int a)
{
    return a + 1;
}

...

// in Main
for (int i = 0; i < iterations; i++)
{
    s = Increment(s);
}

5 次运行的结果:

  1. 2161毫秒
  2. 2159 毫秒
  3. 2194 毫秒
  4. 2177毫秒
  5. 2163毫秒

哎哟! 显然,该方法存在开销。

我尝试使用反射,并MethodBase.GetCurrentMethod().NameIncrement方法中打印;它确实在打印Increment- 这意味着该方法没有内联。

接下来我尝试将[MethodImpl(MethodImplOptions.NoInlining)]属性添加到方法中 -但基准时间保持完全相同

在 Debug 模式下,Optimize Code 设置为 false,第一个测试稍慢,而第二个测试大约慢两倍;再次,NoInlining属性不会影响性能。


我在这里做错了什么,以至于即使如此简单的方法也无法在没有开销的情况下工作?为什么会这样?
这肯定不是预期的行为 - 或者是吗?

注意:Java 中的类似测试没有显示此类方法调用的开销。(使用 Eclipse + JDK 1.7,Java 在这方面似乎也快了很多。)

4

1 回答 1

10

如果您在 Visual Studio 中运行程序,请确保使用Start without Debugging命令;否则,可能会禁用内联等一些优化。

如果我首先使用Start without Debugging命令运行程序,然后附加调试器并查看循环的 x86 反汇编,for无论循环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
于 2012-06-21T03:45:11.740 回答