我正在阅读有关JIT优于预编译的优点,其中一个提到JIT可以根据实际运行时数据调整分支预测。自从我在大学编写编译器以来已经很长时间了,但在我看来,在大多数情况下(没有明确的getos),预编译代码也可以实现类似的东西。
请考虑以下代码:
test x
jne L2:
L1: ...
jmp L3:
L2: ...
L3:
如果我们有一些运行时检测可以看到'jne L2'为真的次数,它可以物理地交换L1:block和L2:block中的所有指令。当然,它必须知道在交换期间任何一个块都没有线程,但这些都是细节......
test x
jeq L1:
L2: ...
jmp L3:
L1: ...
L3:
我知道程序代码加载到只读内存等时也存在问题,但这是一个想法。
所以我的问题是,这样的JIT优化是否适用于C / C ++,还是我错过了为什么不能这样做的一些根本原因?那里有C / C ++的JIT优化器吗?
答案 0 :(得分:8)
我所知道的C ++没有JIT编译器;但是,GCC确实支持反馈定向优化(FDO),它可以使用运行时分析来优化分支预测等。
请参阅以“-fprofile”开头的GCC options(提示:“ - fprofile-use”使用生成的运行时配置文件执行优化,而“-fprofile-generate”用于生成运行时配置文件)
答案 1 :(得分:6)
大多数现代CPU支持分支预测。它们有一个小缓存,允许CPU在概念上为您提供在运行时重新排序的好处。此缓存的大小相当有限,但可能意味着您没有获得您想象的那么多的好处。有些CPU甚至可以开始执行两个分支,并丢弃未在分支上完成的工作。
编辑:使用JIT编译器的最大优势来自于这样的代码。
if (debug) {
// do something
}
JIT非常擅长检测和优化无法执行任何操作的代码。 (如果你有一个微基准测试表明Java比C快得多,那么JIT很可能检测到你的测试没有做任何C编译器没做的事情)
你可能会问,为什么C不会有这样的东西?因为它有“更好”的东西
#if DEBUG
// do something
#endif
这是最优的,只要DEBUG很少更改,并且您只有很少的这些标志,因此您可以编译每个有用的组合。
这种方法的问题是可扩展性。您添加的每个标志都可以使预先编译的二进制文件的数量翻倍。
如果你有很多这样的标志,并且编译每个组合是不切实际的,你需要依靠分支预测来动态优化你的代码。
答案 2 :(得分:1)
您正在引用跟踪或重新优化JIT,而不仅仅是任何旧的JIT,这样的事情尚未针对C或C ++(至少不是公开的)。但是,你可能想要检查LLVM是否使用Clang或GCC前端的分支(考虑它的编译器和JIT)不是那种方式,因为我似乎有些主题表明它可能已经实现。
答案 3 :(得分:0)
HP Dynamo二进制重新编译器证明,对于C ++编译器生成的优化代码,可以实现高达20%的加速。 Dynamo并不完全是JIT编译器,因为它以任意机器代码而不是某些更高级别的表示形式(如JVM字节码或.NET CIL)开始,但原则上,C ++的JIT只能比Dynamo更有效。请参阅:http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.12.7138&rank=1
Dynamo是为HP PA-RISC架构而创建的,从未作为商业产品提供,因此它在当前由x86变体主导的世界中没有多大用处。我想知道VMware,Connectix或Parallels是否曾经为他们的重新编译器添加优化传递,或者他们已经摆脱了二进制翻译,转而支持最新x86 CPU中的虚拟化功能。