Comprensión de JIT en PHP 8

La traducción del artículo fue preparada en la víspera del inicio del curso "Backend-developer in PHP"








TL; DR



El compilador Just In Time en PHP 8 se implementa como parte de la extensión Opcache y está diseñado para compilar el código operativo en las instrucciones del procesador en tiempo de ejecución.



Esto significa que con JIT, Zend VM no tiene que interpretar algunos códigos operativos, tales instrucciones se ejecutarán directamente como instrucciones de nivel de procesador.



JIT en PHP 8



Una de las características más comentadas de PHP 8 es el compilador Just In Time (JIT). Se escucha en muchos blogs y comunidades: hay mucha expectación al respecto, pero hasta ahora no he encontrado muchos detalles sobre cómo funciona JIT en detalle.



Después de muchos intentos y frustraciones para encontrar información útil, decidí estudiar el código fuente de PHP. Combinando mi pequeño conocimiento de C con toda la información dispersa que he podido reunir hasta ahora, he logrado preparar este artículo y espero que te ayude a comprender PHP JIT mejor.



Para simplificar las cosas: cuando JIT funciona como se esperaba, su código no se ejecutará a través de la máquina virtual Zend, sino que se ejecutará directamente como un conjunto de instrucciones a nivel de procesador.



Esta es toda la idea.



Pero para entender esto mejor, tenemos que pensar en cómo funciona php internamente. No es muy difícil, pero requiere algo de introducción.



Ya he escrito un artículo con una descripción rápida de cómo funciona php . Si cree que este artículo se está volviendo demasiado complicado, solo lea su predecesor y vuelva. Esto debería facilitar un poco las cosas.



¿Cómo se ejecuta el código PHP?



Todos sabemos que php es un lenguaje interpretado. Pero, ¿qué significa esto realmente?



Siempre que desee ejecutar código PHP, ya sea un fragmento o una aplicación web completa, debe pasar por el intérprete php. Los más utilizados son PHP FPM y el intérprete CLI. Su trabajo es muy simple: obtener el código php, interpretarlo y devolver el resultado.



Esta es una imagen común para cada idioma interpretado. Algunos pasos pueden variar, pero la idea general es la misma. En PHP funciona así:



  1. El código PHP se lee y se convierte en un conjunto de palabras clave conocidas como Tokens. Este proceso le permite al intérprete comprender en qué parte del programa está escrito cada fragmento de código. Este primer paso se llama Lexing o Tokenizing .
  2. , PHP . (Abstract Syntax Tree — AST) , (parsing). AST , , . , «echo 1 + 1» « 1 + 1» , , « , — 1 + 1».
  3. AST, , . -, , (Intermediate Representation IR), PHP (Opcode). AST .
  4. Ahora que tenemos los códigos de operación, viene lo más interesante: ¡ la implementación del código! PHP tiene un motor llamado Zend VM que es capaz de obtener una lista de códigos de operación y ejecutarlos. Después de que se hayan ejecutado todos los códigos de operación, el programa finaliza.




Para hacerlo un poco más claro, hice un diagrama:





un diagrama simplificado del proceso de interpretación de PHP.



Bastante sencillo como puedes ver. Pero también hay un cuello de botella aquí: ¿cuál es el punto de eliminar y analizar su código cada vez que lo ejecuta si su código php puede no cambiar tan a menudo?



Después de todo, solo nos interesan los códigos de operación, ¿verdad? ¡Correcto! Es por eso que existe la extensión Opcache .



Extensión Opcache



La extensión Opcache viene con PHP y generalmente no hay una razón particular para desactivarla. Si está utilizando PHP, probablemente debería habilitar Opcache.



Lo que hace es agregar una capa de caché de código de operación compartida en línea. Su trabajo es obtener los códigos de operación generados recientemente de nuestro AST y almacenarlos en caché para que las ejecuciones posteriores puedan omitir fácilmente las fases de lexing y análisis.



Aquí hay un diagrama del mismo proceso con la extensión Opcache en mente:





flujo de interpretación PHP con Opcache. Si el archivo ya se ha analizado, php extrae el código de operación en caché para él, en lugar de volver a analizarlo.



Es fascinante lo bien que se saltan los pasos de lexing, análisis y compilación.

Nota : ¡Aquí es útil la función de precarga de PHP 7.4 ! Esto le permite decirle a PHP FPM que analice su base de código, la convierta en códigos de operación y los almacene en caché incluso antes de que realmente haga algo.


Puede comenzar a preguntarse dónde puede pegar JIT aquí, ¿verdad? Al menos eso espero, por eso estoy escribiendo este artículo ...



¿Qué hace el compilador Just In Time?



Después de escuchar la explicación de Ziv en un episodio de podcasts PHP y JIT de PHP Internals News , pude tener una idea de lo que se supone que debe hacer el JIT ...



Si Opcache permite una recuperación más rápida del código de operación para que pueda saltar directamente al Zend VM, JIT destinado a hacerlo funcionar sin Zend VM en absoluto.



Zend VM es un programa en C que actúa como una capa entre el código operativo y el propio procesador. El JIT genera el código compilado en tiempo de ejecución, por lo que php puede omitir la máquina virtual Zend y saltar directamente al procesador . En teoría, deberíamos beneficiarnos de esto en términos de rendimiento.



Al principio sonó extraño, porque para compilar código de máquina, debes escribir una implementación muy específica para cada tipo de arquitectura. Pero, de hecho, es bastante real.



La implementación JIT en PHP utiliza la biblioteca DynASM (Dynamic Assembler) , que asigna un conjunto de instrucciones de CPU en un formato específico al código de ensamblaje para muchos tipos diferentes de CPU. Por lo tanto, el compilador Just In Time convierte el código operativo en código de máquina específico de la arquitectura usando DynASM.



Aunque un pensamiento todavía me perseguía ...



Si la precarga es capaz de analizar el código php a operativo antes de la ejecución, y DynASM puede compilar código operativo a código de máquina (compilación Just In Time), ¿por qué diablos no compilamos PHP en su lugar utilizando la compilación Ahead of Time?



Una de las ideas que obtuve del episodio de podcast fue que PHP está tipado débilmente, lo que significa que PHP a menudo no sabe de qué tipo es una variable hasta que Zend VM intenta ejecutar un código de operación específico.



Puede entender esto mirando el tipo de unión zend_value , que tiene muchos punteros a diferentes representaciones de tipo para una variable. Cada vez que Zend VM intenta obtener un valor de zend_value, usa macros como ZSTR_VALque intentan acceder al puntero de cadena desde la concatenación de valores.



Por ejemplo, este controlador Zend VM debe manejar la expresión menor o igual que (<=). Vea cómo se ramifica en muchas rutas de código diferentes para adivinar los tipos de operandos.



Duplicar este tipo de lógica de inferencia con código de máquina no es factible y podría hacer que las cosas sean aún más lentas.



La compilación final después de que los tipos han sido evaluados tampoco es una buena opción porque compilar en código máquina es una tarea intensiva de CPU. Así que compilar TODO en tiempo de ejecución es una mala idea.



¿Cómo se comporta el compilador Just In Time?



Ahora sabemos que no podemos deducir tipos para generar una compilación previa suficientemente buena. También sabemos que la compilación en tiempo de ejecución es costosa. ¿Cómo puede ser útil JIT para PHP?



Para equilibrar esta ecuación, PHP JIT intenta compilar solo unos pocos códigos de operación que cree que valen la pena. Para hacer esto, perfila los códigos de operación ejecutados por la máquina virtual Zend y comprueba cuáles tienen sentido compilar. (dependiendo de su configuración) .



Cuando se compila un código de operación en particular, luego delega la ejecución a ese código compilado en lugar de delegarlo en la VM Zend. Se ve como el diagrama a continuación:





flujo de interpretación PHP con JIT. Si ya están compilados, los códigos de operación no se ejecutan a través de la VM Zend.



Por lo tanto, hay un par de instrucciones en la extensión Opcache que determinan si cierto código operativo debe compilarse o no. Si es así, el compilador lo convierte en código de máquina usando DynASM y ejecuta este código de máquina recién generado.



Curiosamente, dado que la implementación actual tiene un límite de megabytes para el código compilado (también configurable), la ejecución del código debería poder cambiar sin problemas entre JIT y el código interpretado.



Por cierto, esta charla de Benoit Jacquemont sobre JIT de php me ayudó MUY a entender esto.



Todavía no estoy seguro de en qué casos específicos tiene lugar la compilación, pero creo que realmente no quiero saberlo todavía.



Por lo tanto, su aumento de productividad probablemente no será colosal



Espero que ahora sea mucho más claro POR QUÉ todos dicen que la mayoría de las aplicaciones php no obtendrán muchos beneficios de rendimiento al usar el compilador Just In Time. Y por qué la recomendación de Ziv para perfilar y experimentar con diferentes configuraciones JIT para su aplicación es la mejor manera de hacerlo.



Los códigos de operación compilados generalmente se distribuirán entre múltiples solicitudes si está utilizando PHP FPM, pero esto aún no cambia las reglas del juego.



Esto se debe a que JIT optimiza las operaciones de la CPU, y hoy en día la mayoría de las aplicaciones php están más vinculadas a E / S que cualquier otra cosa. No importa si las operaciones de procesamiento se compilan si tiene que acceder al disco o la red de todos modos. Los horarios serán muy similares.



Si solo...



Estás haciendo algo que no es de E / S, como el procesamiento de imágenes o el aprendizaje automático. Cualquier otra cosa que no sea E / S se beneficiará del compilador Just In Time. Esta es también la razón por la cual las personas ahora dicen que se inclinan más hacia la escritura de funciones PHP nativas escritas en PHP en lugar de C. La sobrecarga no será dramáticamente diferente si tales funciones se compilan de todos modos.



Un momento interesante como programador de PHP ...



Espero que este artículo te haya sido útil y entiendas mejor qué es JIT en PHP 8. No dudes en enviarme un tweet si quieres agregar algo que podría haber olvidado aquí, y no olvides compartir esto con tus compañeros desarrolladores, ¡seguramente agregará un poco de valor a tus conversaciones!-- @nawarian






PHP:







All Articles