Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can't get even a simple method to inline (?)

Logically, I always assumed that very short and simple methods would get inlined by the C# Compiler and thus won't show any overhead compared to simply typing the code in the methods by hand...

Until today - when I tried to benchmark various methods and manually inlined code. Turns out that (for me) even the most simple code will show a method call overhead compared to its manually inlined counterpart.
Actually, I could find no clues as to any method being inlined - So I ran a simple test.

System used:

  • Intel C2D E7200 (Dual Core) 2.53GHz
  • 4GB DDR2
  • Windows 7 64bit
  • .NET 4.0
  • Visual Studio 2010 Ultimate

All tests were performed without Debug and using Release Configuration (Optimize Code).

Here's the code I used to benchmark:

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] First, I've simply benchmarked a simple increment command:

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

Results from 5 runs:

  1. 867ms
  2. 877ms
  3. 868ms
  4. 865ms
  5. 870ms

[2] Switching to a method call:

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

...

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

Results from 5 runs:

  1. 2161ms
  2. 2159ms
  3. 2194ms
  4. 2177ms
  5. 2163ms

Ouch! Obviously there's an overhead for the method.

I've tried to use reflection, and printed MethodBase.GetCurrentMethod().Name from within the Increment method; it was indeed printing Increment - meaning the method isn't inlined.

Next I've tried to add the [MethodImpl(MethodImplOptions.NoInlining)] attribute to the method - but the benchmark time stayed exactly the same.

In Debug mode and with Optimize Code set to false, the first test is slightly slower while the second one is about two times slower; and yet again the NoInlining attribute doesn't impact performance.


Am I doing something wrong here that I fail to make even such a simple method work without an overhead? Why is this happening?
Surely this can't be expected behavior - or is it?

Note: Similar testing in Java shows no overhead to such a method call. (using Eclipse + JDK 1.7, Java also seems to be A LOT faster at this.)

like image 712
Acidic Avatar asked Aug 06 '26 11:08

Acidic


1 Answers

If you're running the program from within Visual Studio, make sure you use the Start Without Debugging command; otherwise, some optimizations like inlining may be disabled.

If I first use the Start Without Debugging command to run the program, then attach the debugger and look at the x86 disassembly of the for loop, I get the same thing whether the loop increments s directly or calls Increment; that is, the method call is inlined:

00000049  xor         eax,eax
0000004b  inc         ebx
0000004c  inc         eax
0000004d  cmp         eax,0EE6B280h
00000052  jl          0000004B

In contrast, if I use the Start Debugging command to run the program, then the method call is not inlined:

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
like image 76
Michael Liu Avatar answered Aug 08 '26 01:08

Michael Liu