结合内联和非可变布尔模块属性的场景中的JIT优化

时间:2011-01-22 04:56:14

标签: .net optimization f# jit

在以下计划中,

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

如果reliesOnCondition System.DateTime.Now.Second在模块加载时结果为假,JIT会优化表达式condition吗?

2 个答案:

答案 0 :(得分:4)

没有*。当F#为程序发出IL时,对condition的所有访问都是通过属性访问器完成的。由于这个额外的间接层,JIT引擎无法优化reliesOnCondition的整个主体。

让我说明我是如何发现这一点的。 (也列在old blog帖子上。)

创建新的F#项目'SOQ'

构建我们的F#应用程序。我只是将条件硬编码为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

将其拆解并在PDB中使用IL操作码重新组装

接下来使用ildasm使用/SOURCE参数反汇编IL二进制文件。这不仅可以获得源的IL转储,还可以包含留下作为注释的原始源代码。

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

从IL重新组合我们的二进制文件

接下来使用ilasm重新组合IL二进制文件,但传入/DEBUG标志以获取PDB。生成的应用程序将具有两个级别的代码。首先,原始F#将作为注释保留,实际代码将是IL指令。

ilasm SOQ-annotated.exe.il /DEBUG

运行该过程并附加Visual Studio调试器

运行新注释的程序。这将导致应用程序像正常一样进行JIT-ted。接下来,将Visual Studio调试器附加到活动进程。

逐步执行代码

在VS调试器中查看IL转储是不够的。右键单击“堆栈跟踪”窗口并选中转到反汇编。这将显示实际的x86指令。

这是x86操作码转储。请注意原始F#源代码行(ildasm /SOURCE),其下面的IL指令(ilasm /DEBUG)以及下面的x86指令(由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 ...

如您所见,IL指令34调用Program::get_condition(),因此JIT没有足够的信息来正确消除无操作函数调用。 (请注意,只有函数可以内联标记,因此您不能再进一步标记。)

* 在我的机器上(x64 Win7)。 x86和x64 JIT引擎之间存在差异,以及您是否使用NGEN生成可执行文件。您的里程可能会有所不同。

答案 1 :(得分:2)

upvoted答案是不正确的,属性访问器间接通常不会阻止JIT优化器省略死代码。大多数简单内容都会被内联,它们的编译时值被考虑在内。这意味着它不会优化表达式,因为 condition 的值仅在运行时已知。在 condition 值已知之后代码被jitted的事实不会改变这一点,优化器必须能够静态地确定该值。只有一个文字就足够了,你得到一个条件编译(C#中的#if)。有关优化程序的更多背景信息,请查看this answer