为什么.net使用JIT编译器而不是仅在目标机器上编译一次代码?

标题几乎总结了一下,但我想知道为什么像.net这样的系统每次运行时都会编译代码,而不是只在目标机器上编译一次?

使用.NET或Java等中间格式可以获得两件事:

  1. 您可以在任何平台上运行该程序,因为代码是以中间格式而不是本机代码表示的。 您只需要为中间格式编写解释器。
  2. 它允许在编译时不能(轻松)实现某些运行时优化:例如,您可以利用新CPU上的特殊function,即使在编写程序时这些CPU不存在 – 只有JIT编译器需要知道这一点。

现在,至于为什么你可能不想在第一次运行时执行编译然后只缓存它 – 这也可能有几个原因。

如果你在启动之前编译,那么用户必须等待很长时间才能进行第一次运行 – 在那个时间点,你无法知道用户将实际使用什么。 通过只编译你需要的东西,当你需要它时,你可以更快地开始,只是因为你没有多少工作要做,而且你没有存储很多用户永远不会使用的代码(对于大型程序可以是很多代码)。

如果您开始跨会话缓存JIT代码,则需要跟踪已编译的内容,然后将其存储到磁盘。 对于大型程序,您可能需要从磁盘加载大量本机代码。 磁盘I / O非常昂贵,因此等待磁盘可能需要更长的时间而不是重新JIT它。 此外,您需要跟踪缓存可用的时间。 如果硬件发生更改,您可能需要重新JIT才能应用一些新的优化。 如果程序发生变化,则不能使用任何旧的编译代码。 如果运行时编译器发生更改,则可能已修复安全性错误,您需要重新编译以确保错误不会保留在本机代码中。

基本上,JIT编译器突然有很多工作要做(包括处理缓存的磁盘I / O),并且变得更加复杂和缓慢,降低了JIT的点。

现在,这并不意味着预编译某些程序集有时不会有利,正如Matthew Ferreira指出的那样,ngen工具可以做什么 – 但在一般情况下,它不值得这样做,因为JIT编译通常不够快。

我不编写编译器或运行时,所以我不能肯定地说这个,但我不相信每次都是及时的。 它只是意味着我们等到我们真的需要编译,但是一旦为该实例完成编译,它就不会再次编译。

如果将可执行文件移动到另一台机器怎么办? 如果为第一台机器的特定处理器编译了一次,它将无法利用新处理器上可用的(可能)更高级或更高效的指令集。 如果您确实想在目标计算机上编译一次,请考虑在应用程序安装期间使用ngen.exe 。

JIT编译的另一个优点:10年或15年前编写的代码只需使用最先进的JIT运行即可获得显着的性能提升,而无需对代码进行任何更改。

标题几乎总结了一下,但我想知道为什么像.net这样的系统每次运行时都会编译代码,而不是只在目标机器上编译一次?

.NET确实将CIL的编译结果缓存到给定目标上的本机代码,但这并非易事:

  • 在从磁盘加载缓存数据和即时重新编译之间需要权衡。
  • 任何forms的运行时代码生成都需要能够即时编译。 这不仅是System.Reflection.Emit的使用,还包括动态加载的程序集中的类型的统一,甚至在多态递归期间创建新的值类型。

如果您直接编译为机器代码,那么您已针对特定系统:

代码 - >机器代码

但是,在.NET,Java等中,您所针对的是虚拟机 。 这将生成任何符合.NET的VM可以解释的代码,并依次将JIT转换为真实的机器代码。

那么,在.NET中:

代码 - > IL  - > VM(执行) - > JIT'd机器代码

最初它看起来更复杂,但考虑针对多种架构:

代码(x86) - >机器代码(x86)
代码(ppc) - >机器代码(ppc)
等...(每个目标1x代码集)

代码 - > IL  - > VM(x86)(执行) - > JIT'd x86机器代码
                 VM(ppc)(执行) - > JIT'd ppc机器码
                等等...

我的插图很粗略,但您会注意到单个.NET代码包可以在多个平台上定位和运行