JIT编译器与普通编译器有何不同?


22

有关Java,Ruby和Python之类的JIT编译器的宣传大肆宣传。JIT编译器与C / C ++编译器有何不同?为什么为Java,Ruby或Python编写的编译器称为JIT编译器,而C / C ++编译器仅称为编译器?

Answers:


17

JIT编译器会在执行之前或什至已经执行时即时对其进行编译。这样,运行代码的VM可以检查代码执行中的模式,以允许仅使用运行时信息才能进行的优化。此外,如果VM出于某种原因(例如,太多的高速缓存未命中或代码频繁抛出特定异常)而决定编译版本的性能不够好,则它可能会决定以不同的方式重新编译它,从而导致更智能汇编。

另一方面,C和C ++编译器传统上不是JIT。它们只能在开发人员的计算机上一次编译一次,然后生成可执行文件。


是否有JIT编译器可以跟踪缓存未命中并相应地调整其编译策略?@Victor
Martin Berger

@MartinBerger我不知道是否有可用的JIT编译器可以这样做,但是为什么不呢?随着计算机功能越来越强大以及计算机科学的发展,人们可以对程序进行更多优化。例如,Java诞生时,它的运行速度非常慢,可能是已编译Java的20倍,但现在的性能并没有那么落后,可以与某些编译器
相提并论

很久以前,我在某篇博客文章中就听说过这一点。编译器可能会根据当前CPU进行不同的编译。例如,如果它检测到CPU支持SSE / AVX,则它可以自动将某些代码向量化。当有更新的SIMD扩展可用时,它可以编译为更新的SIMD扩展,因此程序员无需更改任何内容。或者,如果它可以安排操作/数据以利用更大的缓存或减少缓存丢失
phuclv 2014年

@LưuVĩnhPhúcChào!我很高兴相信这一点,但是区别在于,例如CPU类型或缓存大小是静态的,在整个计算过程中不会改变,因此也可以由静态编译器轻松完成。高速缓存未命中OTOH非常动态。
Martin Berger 2014年

12

JIT是即时编译器的缩写,名称是misson:在运行时,它确定了有价值的代码优化并加以应用。它不能代替普通的编译器,而是解释器的一部分。请注意,使用中间代码的Java之类的语言都具有:用于将源代码转换为中间代码的普通编译器,以及用于提高性能的解释器中包含的JIT。

代码优化当然可以由“经典”编译器执行,但要注意主要区别:JIT编译器可以在运行时访问数据。这是一个巨大的优势。显然,正确利用它可能很难。

例如,考虑如下代码:

m(a : String, b : String, k : Int) {
  val c : Int;
  switch (k) {
    case 0 : { c = 7; break; }
    ...
    case 17 : { c = complicatedMethod(k, a+b); break; }
  }

  return a.length + b.length - c + 2*k;
}

普通的编译器对此不能做太多。但是,JIT编译器可能检测到mk==0出于某种原因而被调用(类似的事情可能会随着代码随时间的变化而发生)。然后,它可以创建代码的较小版本(并将其编译为本机代码,尽管从概念上讲我认为这是次要的点):

m(a : String, b : String) {
  return a.length + b.length - 7;
}

在这一点上,它可能甚至会内联方法调用,因为它现在很简单。

显然,Sun放弃javac了Java 6中曾经做过的大多数优化。有人告诉我,这些优化使JIT很难做很多事情,而且天真地编译的代码最终运行得更快。去搞清楚。


顺便说一句,在存在类型擦除的情况下(例如Java中的泛型),JIT实际上对类型不利。
拉斐尔

因此,运行时比编译语言要复杂得多,因为运行时环境必须收集数据才能进行优化。
saadtaame

2
在许多情况下,JIT就不能更换m了版本,没有检查k,因为这将是无法证明,m永远不会有一个非零被称为k,但即使没有能够证明它可以取代它与static miss_count; if (k==0) return a.length+b.length-7; else if (miss_count++ < 16) { ... unoptimized code for m ...} else { ... consider other optimizations...}
超级猫

1
我同意@supercat的看法,并认为您的示例实际上具有误导性。只有k=0 始终(这不是输入或环境的函数)才可以安全地放弃测试-但是确定测试需要静态分析,因为静态分析的成本很高,因此只能在编译时负担得起。当通过代码块中的一条路径使用的频率比其他路径高得多时,JIT可以获胜,而专门用于此路径的代码版本将更快。但是JIT仍然会发出测试,以检查快速路径是否适用,如果不是,则采用“慢速路径”。
j_random_hacker

一个例子:一个函数接受一个Base* p参数,并通过它调用虚函数;运行时分析表明,指向始终(或几乎始终)的实际对象似乎是Derived1类型。JIT可以使用对Derived1方法的静态解析(甚至内联)调用来生成函数的新版本。此代码之前有一个条件,该条件检查p的vtable指针是否指向期望的Derived1表;如果不是,它会以较慢的动态解析方法调用跳转到该函数的原始版本。
j_random_hacker
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.