Hace poco uno de los principales desarrolladores de CPython dio a conocer un nuevo compilador JIT para Python utilizando la técnica Copy-and-Patch, la cual es una reciente e innovadora técnica de compilación que se destaca por su velocidad, facilidad de mantenimiento y su completa integración con el intérprete existente.
Copy-and-Patch se basa en el uso de una biblioteca predefinida de fragmentos de código binario conocidos como «plantillas» para emitir un código de máquina optimizado. Estas plantillas son implementaciones prediseñadas de nodos AST (Abstract Syntax Tree) o códigos de operación de bytes que contienen valores faltantes, como literales inmediatos, compensaciones de variables de pila y objetivos de saltos y llamadas.
Permite generar sistemáticamente variantes de plantillas binarias en C++ de manera limpia y pura. Utiliza la infraestructura del compilador Clang+LLVM para ocultar detalles específicos de la plataforma a nivel bajo.
Durante el tiempo de ejecución, la optimización y generación de código se convierten en tareas más simples al buscar una tabla de datos que contenga la plantilla adecuada, crear una instancia de ella y colocarla en la posición deseada mediante el proceso de Copy-and-Patch, ajustando los valores faltantes para ser parcheados en el runtime.
Viéndolo desde una perspectiva más simple, consiste en compilar (Copy) el código fuente existente y sobre ello ajustando los valores faltantes o modificaciones específicas (Patch).
Copy-and-Patch facilita en gran medida la conversión automática de un intérprete escrito en lenguaje C en un compilador JIT, eliminando la necesidad de crear lógica de generación de código y representaciones de compilación por separado. Al utilizar un generador de código común, corregir errores en el intérprete resulta en la solución automática de los mismos problemas en JIT.
El enfoque Copy-and-Patch se apoya en la similitud entre reubicar código en la memoria cuando el vinculador carga archivos objeto y sustituir instrucciones de la máquina en lugar de código de bytes en JIT son tareas similares. Durante la ejecución del programa, las instrucciones de código de bytes generadas por el intérprete se enumeran, y el código de máquina precompilado se copia para cada instrucción en un área de memoria ejecutable, luego de ello las instrucciones se modifican dinámicamente para sustituir los datos procesados en tiempo real. En el caso de JIT, se copian plantillas predefinidas de funciones ya compiladas y las sustituye por los valores necesarios, como argumentos y constantes).
La implementación de un JIT con la técnica Copy-and-Patch implica compilar un archivo objeto en formato ELF utilizando LLVM. Este archivo objetó contiene información sobre las instrucciones de código de bytes y detalles sobre el reemplazo de datos necesario. Durante la ejecución, JIT reemplaza las instrucciones de código de bytes generadas por el intérprete con representaciones de código de máquina, ajustando simultáneamente los datos necesarios para los cálculos. Aunque la implementación JIT requiere LLVM como dependencia durante la compilación, los componentes del tiempo de ejecución no están vinculados a dependencias externas, reduciéndose a aproximadamente 300 líneas de código C escritas a mano y 3000 líneas de código C generadas.
En términos de rendimiento, el JIT propuesto con la técnica Copy-and-Patch presenta notables mejoras en comparación con enfoques tradicionales. Al contrastarlo con los JIT convencionales (LLVM -O0), se destaca por una generación de código 100 veces más rápida y un código resultante que es un 15% más eficiente. En el ámbito de la compilación en WebAssembly (Liftoff), el nuevo JIT demuestra una generación de código 5 veces más veloz, y el código resultante se ejecuta un 50% más rápido.
Al compararse con un JIT de optimización como LuaJIT, que emplea código ensamblador escrito manualmente, el JIT propuesto superó en velocidad en 13 de 44 pruebas. Aunque en promedio se quedó atrás en rendimiento en un 35%, es esencial destacar que esta diferencia se ve compensada por una simplificación significativa en el mantenimiento y una reducción en la complejidad de la implementación. Este equilibrio entre rendimiento y eficiencia en la gestión del código posiciona al JIT propuesto como una alternativa atractiva en el panorama del rendimiento.
Finalmene si estás interesado en poder conocer más al respecto, puedes consultar los detalles en el siguiente enlace.