Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are Methods using the ?: Operator Inlined during JIT compilation?

I was just wondering if a simple static function using the ?: operator is inlined during just in time compilation. Here is an arbitrary example using the code.

    public static int Max(int value, int max)
    {
        return value > max ? max : value;
    }

Which gives the following IL:

Max:
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldarg.1     
IL_0003:  bgt.s       IL_0008
IL_0005:  ldarg.0     
IL_0006:  br.s        IL_0009
IL_0008:  ldarg.1     
IL_0009:  stloc.0     
IL_000A:  br.s        IL_000C
IL_000C:  ldloc.0     
IL_000D:  ret 

Or would the simplier alternative be inlined?

        public static int Max(int value, int max)
        {
            if (value > max)
            {
                return max;
            }
            else
            {
                return value;
            }
        }

Here is the IL for it:

Max:
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldarg.1     
IL_0003:  cgt         
IL_0005:  stloc.0     
IL_0006:  ldloc.0     
IL_0007:  brfalse.s   IL_000E
IL_0009:  nop         
IL_000A:  ldarg.1     
IL_000B:  stloc.1     
IL_000C:  br.s        IL_0013
IL_000E:  nop         
IL_000F:  ldarg.0     
IL_0010:  stloc.1     
IL_0011:  br.s        IL_0013
IL_0013:  ldloc.1     
IL_0014:  ret

The ?: operator apparently generates a more concise MSIL than the if alternative, but does anyone know what happens during JIT compilation? Are both of them inlined? Are either of them inlined?

like image 486
Krythic Avatar asked Nov 24 '15 22:11

Krythic


1 Answers

How could we find out? Let's just take a look at the generated code.

Here's a test program:

internal static class Program
{
    public static int MaxA(int value, int max)
    {
        return value > max ? max : value;
    }

    public static int MaxB(int value, int max)
    {
        if (value > max)
            return max;
        else
            return value;
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static int TestA(int a, int b)
    {
        return MaxA(a, b);
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static int TestB(int a, int b)
    {
        return MaxB(a, b);
    }

    private static void Main()
    {
        var rand = new Random();
        var a = rand.Next();
        var b = rand.Next();

        var result = TestA(a, b);
        Console.WriteLine(result);

        result = TestB(a, b);
        Console.WriteLine(result);
    }
}

First, let's get some things straight. In a Release build, the IL of MaxA is (on Roslyn):

.method public hidebysig static 
    int32 MaxA (
        int32 'value',
        int32 max
    ) cil managed 
{
    // Method begins at RVA 0x2050
    // Code size 8 (0x8)
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: ldarg.1
    IL_0002: bgt.s IL_0006

    IL_0004: ldarg.0
    IL_0005: ret

    IL_0006: ldarg.1
    IL_0007: ret
} // end of method Program::MaxA

For MaxB, it's:

.method public hidebysig static 
    int32 MaxB (
        int32 'value',
        int32 max
    ) cil managed 
{
    // Method begins at RVA 0x2059
    // Code size 8 (0x8)
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: ldarg.1
    IL_0002: ble.s IL_0006

    IL_0004: ldarg.1
    IL_0005: ret

    IL_0006: ldarg.0
    IL_0007: ret
} // end of method Program::MaxB

So the IL is symmetric for both functions (it's the same code, except the ordering of the branches as well as the branch instruction are inverted).

Now, let's check what the x64 code of TestA and TestB looks like.

TestA, x64, RyuJIT:

            return MaxA(a, b);
00007FFED5F94530  cmp         ecx,edx  
00007FFED5F94532  jg          00007FFED5F94538  
00007FFED5F94534  mov         eax,ecx  
00007FFED5F94536  jmp         00007FFED5F9453A  
00007FFED5F94538  mov         eax,edx  
00007FFED5F9453A  ret  

You can see that the MaxA function is inlined (there's no call instruction and you can clearly see the jg "jump if greater" branching instruction).

TestB, x64:

            return MaxB(a, b);
00007FFED5F94550  cmp         ecx,edx  
00007FFED5F94552  jle         00007FFED5F94558  
00007FFED5F94554  mov         eax,edx  
00007FFED5F94556  jmp         00007FFED5F9455A  
00007FFED5F94558  mov         eax,ecx  
00007FFED5F9455A  ret  

Unsurprisingly, we get the same result.

For compelteness, here's MaxA on x86:

            return MaxA(a, b);
00A32E22  in          al,dx  
00A32E23  cmp         ecx,edx  
00A32E25  jg          00A32E2B  
00A32E27  mov         eax,ecx  
00A32E29  jmp         00A32E2D  
00A32E2B  mov         eax,edx  
00A32E2D  pop         ebp  
00A32E2E  ret  

Inlined as well.


For reference, you can check the generated assembly code with the Disassembly window (Debug -> Windows -> Disassembly) when you're on a breakpoint, but first make sure you uncheck the Suppress JIT optimization on module load option:

option to uncheck

like image 91
Lucas Trzesniewski Avatar answered Oct 05 '22 21:10

Lucas Trzesniewski