Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JIT optimization in scenario combining inline and non-mutable boolean module property

In the following program,

module Program

let condition = System.DateTime.Now.Millisecond % 2 = 0

let inline reliesOnCondition (x:int) =
    if condition then
        printfn "%i" x

[<EntryPoint>]
let main args =
    reliesOnConditional System.DateTime.Now.Second
    0

will the JIT optimize away the expression reliesOnCondition System.DateTime.Now.Second if condition turns out to be false upon module loading?

like image 353
Stephen Swensen Avatar asked Jan 22 '11 04:01

Stephen Swensen


2 Answers

No*. When F# emits the IL for the program, all accesses to condition are done through a property accessor. Because of this extra layer of indirection the JIT engine cannot optimize away the entire body of reliesOnCondition.

Let me show how I found this out. (Also listed on an old blog post.)

Create new F# Project 'SOQ'

Build our F# application. I just hard-coded condition to be false.

module Program

let condition = false

let inline reliesOnCondition (x:int) =
    if condition then
        printfn "%i" x

[<EntryPoint>]
let main args =
    printfn "(attach a debugger and press any key)"
    System.Console.ReadKey(true) |> ignore

    reliesOnCondition System.DateTime.Now.Second
    0

Disassemble it and reassemble it with IL opcodes in the PDB

Next use ildasm to disassemble the IL binary using the /SOURCE parameter. This will not only get you an IL dump of the source, but also include the original source code left as comments.

ildasm SOQ.exe /OUT=SOQ-annotated.exe.il /SOURCE

Reassemble our binary from IL

Next use ilasm to reassemble the IL binary, but passing in the /DEBUG flag to get a PDB. The resulting application will have two levels of code. First, the origonal F# will be left in as comments, and the actual code will be IL instructions.

ilasm SOQ-annotated.exe.il /DEBUG

Run the process and attach the Visual Studio debugger

Run the newly annotated program. This will cause the application to get JIT-ted like normal. Next, attach the Visual Studio debugger to the active process.

Step through the code

Looking at an IL dump in the VS debugger isn't sufficient. Right click the 'Stack Traces' window and check Go To Disassembly. This will give you a display of the actual x86-instructions.

Here is the x86 op code dump. Note the original F# source line up top (ildasm /SOURCE), the IL instructions beneath that (ilasm /DEBUG), and the x86 instructions below that (courtesy of Visual Studio).

//000014:     reliesOnCondition System.DateTime.Now.Second
    IL_0026:  call       valuetype [mscorlib]System.DateTime [mscorlib]System.DateTime::get_Now()
000000db  lea         ecx,[ebp-58h] 
000000de  call        595E8C00 
    IL_002b:  stloc.3
000000e3  lea         edi,[ebp-30h] 
000000e6  lea         esi,[ebp-58h] 
000000e9  movq        xmm0,mmword ptr [esi] 
000000ed  movq        mmword ptr [edi],xmm0 
    IL_002c:  ldloca.s   V_3
000000f1  lea         eax,[ebp-30h] 
000000f4  mov         dword ptr [ebp-74h],eax 
    IL_002e:  call       instance int32 [mscorlib]System.DateTime::get_Second()
000000f7  mov         ecx,dword ptr [ebp-74h] 
000000fa  call        5960A670 
000000ff  mov         dword ptr [ebp-5Ch],eax 
    IL_0033:  stloc.2
00000102  mov         eax,dword ptr [ebp-5Ch] 
00000105  mov         dword ptr [ebp-28h],eax 
    IL_0034:  call       bool Program::get_condition()
00000108  call        dword ptr ds:[004232D8h] 
0000010e  mov         dword ptr [ebp-60h],eax 
    IL_0039:  brfalse.s  IL_003d
00000111  cmp         dword ptr [ebp-60h],0 
00000115  je          0000011A 
... snip ...

As you can see, the IL instruction 34 calls Program::get_condition(), so the JIT doesn't have enough information to properly eliminate the no-op function call. (Note that only functions can be marked inline, so you can't really go any further than this.)

*On my machine (x64 Win7). There is a difference between the x86 and x64 JIT engines as well as whether or not you used NGEN to produce your executable. Your mileage may vary.

like image 141
Chris Smith Avatar answered Nov 15 '22 07:11

Chris Smith


The upvoted answer is incorrect, a property accessor indirection does not in general prevent the JIT optimizer from omitting dead code. Most simple ones get inlined, their compile time values are considered. Which means that it will not optimize away the expression since the value of condition is only known at run time. The fact that the code is jitted after the condition value is already known doesn't change this, the optimizer must be able to determine the value statically. Only a literal will suffice here, you'd get one with conditional compilation (#if in C#). Check this answer for more background info on the optimizer.

like image 39
Hans Passant Avatar answered Nov 15 '22 07:11

Hans Passant