Answers:
他们用新的HipHop中间表示(hhir)和新的间接层(其中包含生成hhir的逻辑)替换了TranslatorX64的tracelet,该逻辑实际上是由同名hhir引用的。
从高层次来看,它使用6条指令来执行之前需要的9条指令,如此处所述:“它以相同的类型检查开始,但翻译的主体为6条指令,明显优于TranslatorX64的9条指令”
http://hhvm.com/blog/2027/faster-and-cheaper-the-evolution-of-the-hhvm-jit
这主要是系统设计的人工产物,我们计划最终对其进行清理。TranslatorX64上剩下的所有代码都是发出代码并将翻译链接在一起的机器。TranslatorX64不再提供理解如何翻译单个字节码的代码。
当hhir替换TranslatorX64时,它生成的代码快大约5%,并且在手动检查时看起来更好。我们在产品首次亮相后又进行了一次迷你锁定,并在此基础上又获得了10%的性能提升。要查看其中的一些改进,让我们看一下addPositive函数及其转换的一部分。
function addPositive($arr) { $n = count($arr); $sum = 0; for ($i = 0; $i < $n; $i++) { $elem = $arr[$i]; if ($elem > 0) { $sum = $sum + $elem; } } return $sum; }
该函数看起来像很多PHP代码:它遍历数组,并对每个元素执行某些操作。现在让我们关注第5和第6行以及它们的字节码:
$elem = $arr[$i]; if ($elem > 0) { // line 5 85: CGetM <L:0 EL:3> 98: SetL 4 100: PopC // line 6 101: Int 0 110: CGetL2 4 112: Gt 113: JmpZ 13 (126)
这两行从数组中加载一个元素,将其存储在局部变量中,然后将该局部变量的值与0比较,并根据结果有条件地跳转到某个地方。如果您对字节码中发生的事情有更多的细节感兴趣,可以浏览bytecode.specification。现在和在TranslatorX64天内的JIT都将这段代码分为两个tracelet:一个仅包含CGetM,另一个则包含其余的指令(此处为何发生这种情况的完整说明与之无关,但是主要是因为我们在编译时不知道数组元素的类型是什么。CGetM的翻译可以归结为对C ++辅助函数的调用,并不是很有趣,因此我们将研究第二个tracelet。这是TranslatorX64的正式退休,
cmpl $0xa, 0xc(%rbx) jnz 0x276004b2 cmpl $0xc, -0x44(%rbp) jnle 0x276004b2 101: SetL 4 103: PopC movq (%rbx), %rax movq -0x50(%rbp), %r13 104: Int 0 xor %ecx, %ecx 113: CGetL2 4 mov %rax, %rdx movl $0xa, -0x44(%rbp) movq %rax, -0x50(%rbp) add $0x10, %rbx cmp %rcx, %rdx 115: Gt 116: JmpZ 13 (129) jle 0x7608200
前四行是类型检查,用来验证$ elem中的值和堆栈顶部的值是否是我们期望的类型。如果它们中的任何一个失败,我们将跳转到触发重新生成Tracelet的代码,并使用新类型生成不同专业的机器代码。翻译的内容紧随其后,并且代码有很大的改进空间。第8行有一个空载,第12行的寄存器移动很容易避免,并且第10行和第16行之间有不断传播的机会。这都是TranslatorX64一次使用字节码方法的结果。没有任何受人尊敬的编译器会发出这样的代码,但是为避免这种情况而进行的简单优化根本不适合TranslatorX64模型。
现在,让我们看看在相同的hhvm修订版中,使用hhir翻译的同一个tracelet:
cmpl $0xa, 0xc(%rbx) jnz 0x276004bf cmpl $0xc, -0x44(%rbp) jnle 0x276004bf 101: SetL 4 movq (%rbx), %rcx movl $0xa, -0x44(%rbp) movq %rcx, -0x50(%rbp) 115: Gt 116: JmpZ 13 (129) add $0x10, %rbx cmp $0x0, %rcx jle 0x76081c0
它以相同的类型检查开头,但翻译的主体为6条指令,明显优于TranslatorX64的9条指令。请注意,没有静载或寄存器来记录移动,并且从Int 0字节码开始的立即数0向下传播到第12行的cmp。这是在tracelet和该转换之间生成的hhir:
(00) DefLabel (02) t1:FramePtr = DefFP (03) t2:StkPtr = DefSP<6> t1:FramePtr (05) t3:StkPtr = GuardStk<Int,0> t2:StkPtr (06) GuardLoc<Uncounted,4> t1:FramePtr (11) t4:Int = LdStack<Int,0> t3:StkPtr (13) StLoc<4> t1:FramePtr, t4:Int (27) t10:StkPtr = SpillStack t3:StkPtr, 1 (35) SyncABIRegs t1:FramePtr, t10:StkPtr (36) ReqBindJmpLte<129,121> t4:Int, 0
字节码指令已分解为更小,更简单的操作。隐藏在某些字节码行为中的许多操作都在hhir中明确表示,例如SetL的第6行上的LdStack。通过使用未命名的临时变量(t1,t2等)代替物理寄存器来表示值的流,我们可以轻松地跟踪每个值的定义和使用。这使得琐碎的事情变得很简单:看看是否实际使用了加载的目的地,或者指令的输入之一实际上是3个字节码之前的常量值。有关hhir是什么以及它如何工作的更详尽的解释,请查看ir.specification。
此示例仅显示了与TranslatorX64相比所做的一些改进。在2013年5月将其部署到生产环境中并淘汰TranslatorX64是一个重要的里程碑,但这仅仅是个开始。从那时起,我们实现了更多的优化,这在TranslatorX64中几乎是不可能的,从而使hhvm的效率几乎提高了一倍。通过隔离和减少我们需要重新实现的特定于体系结构的代码量,使hhvm在ARM处理器上运行对于我们的工作也至关重要。请关注即将发布的专门讨论我们的ARM端口的文章,以了解更多详细信息!”