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?
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:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With