我知道如果 Java 的HotSpot JIT 期望编译的开销低于在解释模式下运行方法的开销,它有时会跳过 JIT 编译方法。.NET CLR是否基于类似的启发式工作?
4 回答
注意:这个答案是在“每次运行”的上下文中。每次运行程序时,通常都会对代码进行 JIT 处理。使用ngen或.NET Native也改变了这个故事......
与 HotSpot 不同,CLR JIT每次运行总是只编译一次。它从不解释,也从不根据实际使用情况进行比以前更重的优化重新编译。
当然,这可能会改变,但从 v1 开始就一直如此,我预计它不会很快改变。
优点是它使 JIT 更简单 - 无需考虑已经运行的“旧”代码、基于不再有效的前提撤消优化等。
对 .NET 有利的一点是,大多数 CLR 语言在默认情况下使方法成为非虚拟方法,这意味着可以进行更多的内联。HotSpot 可以内联一个方法,直到它第一次被覆盖,此时它撤消优化(或者在某些情况下做一些聪明的事情以根据实际类型有条件地仍然使用内联代码)。由于需要担心的虚拟方法更少,.NET 可以在很大程度上忽略无法内联任何虚拟的痛苦。
编辑:上面描述了桌面框架。Compact Framework 会在需要时抛出本地代码,并在必要时再次 JIT。但是,这仍然不像 HotSpots 自适应优化。
微框架显然根本不 JIT,而是解释代码。这对于非常受限的设备是有意义的。(我不能说我对微框架了解很多。)
.NET 运行时总是在执行前编译代码 JIT。因此,它永远不会被解释。
您可以在Anders Hejlsberg的CLR Design Choices中找到一些更有趣的读物。特别是部分:
我读到微软决定永远编译 IL,从不解释。指令中的编码类型信息如何帮助解释器更有效地运行?
Anders Hejlsberg:如果解释器可以盲目地按照指令所说的去做,而不需要跟踪堆栈顶部的内容,那么它可以运行得更快。例如,当它看到一个 iadd 时,解释器不必首先弄清楚它是哪种类型的 add,它知道它是一个整数 add。假设有人已经验证了堆栈看起来是正确的,那么可以安全地减少一些时间,并且您关心的是解释器。但是,在我们的例子中,我们从未打算使用 CLR 来定位解释的场景。我们打算始终 JIT [即时编译],并且出于 JIT 的目的,无论如何我们都需要跟踪类型信息。由于我们已经有了类型信息,它实际上并没有给我们买任何东西来把它放在说明中。
Bill Venners:许多现代 JVM [Java 虚拟机] 进行自适应优化,从解释字节码开始。他们在应用程序运行时对其进行分析,以找到在 80% 到 90% 的时间内执行的 10% 到 20% 的代码,然后将其编译为本机。不过,它们不一定及时编译这些字节码。方法的字节码仍然可以由解释器执行,因为它们正在被编译为本机并在后台进行优化。当本机代码准备好时,它可以替换字节码。通过不针对解释的场景,您是否完全排除了在 CLR 中执行的方法?
Anders Hejlsberg:不,我们还没有完全排除这种可能性。我们仍然可以解释。我们只是没有针对口译进行优化。我们没有针对编写只会解释的最高性能解释器进行优化。我认为没有人会再这样做了。对于 10 年前的机顶盒来说,这可能很有趣。但这不再有趣了。JIT 技术已经发展到可以拥有多种可能的 JIT 策略的地步。你甚至可以想象使用一个快速运行的快速 JIT,然后当我们发现我们一直在执行一个特定的方法时,使用另一个花费更多时间并且在优化方面做得更好的 JIT。您可以在 JIT 方面做更多的事情。
将来很高兴看到一些基于跟踪的 JIT 用于内存不足的设备。它主要会解释,找到热点,并将它们转换为汇编程序并缓存它们。我认为这就是谷歌对他们的 Android JIT 所做的事情,而微软研究院正在进行一个基于跟踪的 JIT 的研究项目。
我找到了一篇文章,SPUR: A Trace-Based JIT Compiler for CIL .. 也许其中的某些内容有一天会进入CLR ?
我不相信,而且我认为它永远不应该。
JIT 怎么知道某个方法会被调用多少次?解释的频率不会影响决策吗?
我还会质疑 JIT 编译器在不解释函数本身的情况下如何分析函数以确定解释是否最好。考虑到这一事实(至少一个方法已经通过),简单地编译每个方法以减少尝试确定首先编译哪些方法的开销不是更好吗?