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

发布于 2024-10-13 01:08:50 字数 416 浏览 4 评论 0原文

在下面的程序中,

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

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

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

趁微风不噪 2024-10-20 01:08:51

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

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.

萌︼了一个春 2024-10-20 01:08:50

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

让我展示一下我是如何发现这一点的。 (也列在 旧博客文章。)

创建新的 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 操作码重新汇编

接下来,使用 /SOURCE 参数使用 ildasm 反汇编 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 处理。接下来,将 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 生成可执行文件之间存在差异。您的里程可能会有所不同。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文