结合内联和不可变布尔模块属性的场景中的 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
如果在模块加载时 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
赞成的答案是不正确的,属性访问器间接寻址通常不会阻止 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.
不*。当 F# 为程序发出 IL 时,对
condition
的所有访问都通过属性访问器完成。由于存在这一额外的间接层,JIT 引擎无法优化reliesOnCondition
的整个主体。让我展示一下我是如何发现这一点的。 (也列在 旧博客文章。)
创建新的 F# 项目“SOQ”
构建我们的 F# 应用程序。我只是将条件硬编码为
false
。将其反汇编并使用 PDB 中的 IL 操作码重新汇编
接下来,使用
/SOURCE
参数使用ildasm
反汇编 IL 二进制文件。这不仅会为您提供源代码的 IL 转储,还会包含作为注释保留的原始源代码。从 IL 重新组装我们的二进制文件
接下来使用
ilasm
重新组装 IL 二进制文件,但传入/DEBUG
标志以获取 PDB。生成的应用程序将具有两级代码。首先,原来的F#将作为注释保留,而实际的代码将是IL指令。运行进程并附加 Visual Studio 调试器
运行新注释的程序。这将导致应用程序像平常一样进行 JIT 处理。接下来,将 Visual Studio 调试器附加到活动进程。
单步执行代码
在 VS 调试器中查看 IL 转储是不够的。右键单击“堆栈跟踪”窗口并选中转到反汇编。这将为您显示实际的 x86 指令。
这是 x86 操作代码转储。请注意顶部的原始 F# 源代码行 (
ildasm /SOURCE
)、其下方的 IL 指令 (ilasm /DEBUG
) 以及其下方的 x86 指令(由 Visual Studio 提供) )。正如您所看到的,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 ofreliesOnCondition
.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
.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.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.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).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.