Reemplazaron los tracelets de TranslatorX64 con la nueva representación intermedia de HipHop (hhir), y una nueva capa de indirección en la que reside la lógica para generar hhir, que en realidad se conoce con el mismo nombre, hhir.
Desde un alto nivel, está utilizando 6 instrucciones para hacer lo que antes requería 9 instrucciones, como se señala aquí: "Comienza con los mismos controles de tipo pero el cuerpo de la traducción es 6 instrucciones, significativamente mejor que el 9 de TranslatorX64"
Eso es principalmente un artefacto de cómo está diseñado el sistema y es algo que planeamos limpiar eventualmente. Todo el código que queda en TranslatorX64 es una maquinaria necesaria para emitir código y vincular las traducciones; el código que entendió cómo traducir códigos de bytes individuales se ha ido de TranslatorX64.
Cuando hhir reemplazó TranslatorX64, estaba generando código que era aproximadamente un 5% más rápido y se veía significativamente mejor después de la inspección manual. Seguimos su debut en la producción con otro mini bloqueo y obtuvimos un 10% adicional en ganancias de rendimiento además de eso. Para ver algunas de estas mejoras en acción, veamos una función addPositive y parte de su traducción.
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;
}
Esta función se parece a un montón de código PHP: recorre una matriz y hace algo con cada elemento. Centrémonos en las líneas 5 y 6 por ahora, junto con su código de bytes:
$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)
Estas dos líneas cargan un elemento de una matriz, lo almacenan en una variable local, luego comparan el valor de ese local con 0 y saltan condicionalmente en algún lugar según el resultado. Si está interesado en obtener más detalles sobre lo que está sucediendo en el bytecode, puede hojear bytecode.specification. El JIT, tanto ahora como en los días de TranslatorX64, divide este código en dos trazos: uno solo con el CGetM, luego otro con el resto de las instrucciones (una explicación completa de por qué sucede esto no es relevante aquí, pero es principalmente porque no sabemos en tiempo de compilación cuál será el tipo de elemento de matriz). La traducción del CGetM se reduce a una llamada a una función auxiliar de C ++ y no es muy interesante, por lo que veremos el segundo brazalete. Este compromiso fue el retiro oficial de 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
Las primeras cuatro líneas son verificaciones de tipo que verifican que el valor en $ elem y el valor en la parte superior de la pila son los tipos que esperamos. Si alguno de ellos falla, saltaremos al código que desencadena una retraducción de la pulsera, utilizando los nuevos tipos para generar un fragmento de código de máquina especializado de manera diferente. Sigue la esencia de la traducción, y el código tiene mucho margen de mejora. Hay una carga muerta en la línea 8, un registro fácilmente evitable para registrar el movimiento en la línea 12, y una oportunidad para la propagación constante entre las líneas 10 y 16. Estas son todas las consecuencias del enfoque bytecode-at-a-time utilizado por TranslatorX64. Ningún compilador respetable emitiría un código como este, pero las simples optimizaciones requeridas para evitarlo simplemente no encajan en el modelo TranslatorX64.
Ahora veamos la misma pulsera traducida usando hhir, en la misma revisión hhvm:
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
Comienza con los mismos controles de tipo pero el cuerpo de la traducción tiene 6 instrucciones, significativamente mejor que el 9 de TranslatorX64. Observe que no hay cargas muertas o registro para registrar movimientos, y el 0 inmediato del código de bytes Int 0 se propagó hacia abajo al cmp en la línea 12. Aquí está el hhir que se generó entre el brazalete y esa traducción:
(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
Las instrucciones del código de bytes se han desglosado en operaciones más pequeñas y simples. Muchas operaciones ocultas en el comportamiento de ciertos códigos de byte están explícitamente representadas en hhir, como el LdStack en la línea 6 que forma parte de SetL. Al usar temporarios sin nombre (t1, t2, etc.) en lugar de registros físicos para representar el flujo de valores, podemos rastrear fácilmente la definición y el uso (s) de cada valor. Esto hace que sea trivial ver si el destino de una carga se usa realmente, o si una de las entradas a una instrucción es realmente un valor constante de hace 3 bytecodes. Para una explicación mucho más exhaustiva de qué es hhir y cómo funciona, eche un vistazo a ir.specification.
Este ejemplo mostró solo algunas de las mejoras que hhir realizó sobre TranslatorX64. Implementarlo en la producción y retirar TranslatorX64 en mayo de 2013 fue un gran hito, pero fue solo el comienzo. Desde entonces, hemos implementado muchas más optimizaciones que serían casi imposibles en TranslatorX64, haciendo que hhvm sea casi el doble de eficiente en el proceso. También ha sido crucial en nuestros esfuerzos para que hhvm se ejecute en procesadores ARM aislando y reduciendo la cantidad de código específico de arquitectura que necesitamos reimplementar. ¡Esté atento a una próxima publicación dedicada a nuestro puerto ARM para obtener más detalles! "