[PyVigo] Erlang: The Road to the JIT

Jesus Cea jcea en jcea.es
Vie Dic 18 18:57:17 CET 2020


Ya sé que esto es Erlang, pero me parece un artículo interesante y 
relevante para la tertulia del martes pasado y la discusión de por qué 
CPYTHON no tiene JIT de serie.

https://blog.erlang.org/the-road-to-the-jit/

Tema "guardas" que comentaba en en la tertulia del martes. Las "guardas" 
con comprobaciones que confirman invariantes para ejecutar código 
optimizado. Por ejemplo, una invariante podría ser que un tipo concreto 
sigue siendo un entero porque antes lo fue.

Ejemplo:

Sea el código:

"""
def suma(n):
     s = 0
     for i in range(n):
         s += i
     return s
"""

Python 3.9 compila este código al siguiente bytecode:

"""
 >>> dis.dis(suma)
   2           0 LOAD_CONST               1 (0)
               2 STORE_FAST               1 (s)

   3           4 LOAD_GLOBAL              0 (range)
               6 LOAD_FAST                0 (n)
               8 CALL_FUNCTION            1
              10 GET_ITER
         >>   12 FOR_ITER                12 (to 26)
              14 STORE_FAST               2 (i)

   4          16 LOAD_FAST                1 (s)
              18 LOAD_FAST                2 (i)
              20 INPLACE_ADD
              22 STORE_FAST               1 (s)
              24 JUMP_ABSOLUTE           12

   5     >>   26 LOAD_FAST                1 (s)
              28 RETURN_VALUE
"""

Aquí se podrían optimizar cosas como reconocer "range", pero 
centrándonos en el cuerpo del bucle, se podría reconocer perfectamente 
que tanto "s" como "i" son enteros y operar con ellos directamente como 
tipos de datos en ensamblador.

Estudiando el código generado, se puede ver que es muy mejorable. Por 
ejemplo, se podría mover la línea 16 a antes del bucle y eliminar las 
líneas 22 y 26, manteniendo "s" en el stack en vez de guardarlo en una 
variable para cargarlo inmediatamente después. De hecho se podría hasta 
eliminar la actualización de "s" en memoria hasta el final del bucle. 
También se podría prescindir de guardar el contador del bucle en la 
variable "i", eliminando las líneas 14 y 18. ¡El cuerpo del bucle 
pasaría de 5 instrucciones a una!. La única complejidad sería la gestión 
de excepciones: Si la suma levanta una excepción, habría que reconstruir 
las variables "i" y "s".

La guarda se podría poner en el INPLACE_ADD, pero haciendo inferencia de 
tipos trivial se podría compilar a ensamblador todo el cuerpo del bucle, 
y la guarda sería simplemente comprobar que ni "i" ni "s" se pasan de 
32/64 bits, que sería el tipo básico en ensamblador. Mientras ni "i" ni 
"s" se pasen de ese rango, se pueden manejar en registros de ensamblador 
y hacer una conversión a tipos Python al terminar el bucle. Si te pasas 
del rango, pues simplemente ejecutas el bytecode habitual, algo 
transparente para el programa.

El intérprete de bytecode tiene el siguiente fragmento para ejecutar 
"INPLACE_ADD":

"""
case TARGET(INPLACE_ADD): {
             PyObject *right = POP();
             PyObject *left = TOP();
             PyObject *sum;
             if (PyUnicode_CheckExact(left) && 
PyUnicode_CheckExact(right)) {
                 sum = unicode_concatenate(tstate, left, right, f, 
next_instr);
                 /* unicode_concatenate consumed the ref to left */
             }
             else {
                 sum = PyNumber_InPlaceAdd(left, right);
                 Py_DECREF(left);
             }
             Py_DECREF(right);
             SET_TOP(sum);
             if (sum == NULL)
                 goto error;
             DISPATCH();
         }
"""

Se ve una optimización cuando los operandos son unicode, pero en el caso 
de números el código es más complejo.

El problema fundamental de Python es que técnicamente podrías capturar 
el "traceback" de otro hilo y examinar sus tipos locales, pero una 
solución evidente sería retrasar esa captura hasta que se llega al final 
del cuerpo del bucle (no al final del bucle, si no del cuerpo del 
bucle). Otro problema es la gestión de excepciones, que habría que 
reconstruir el traceback y los valores de variables "optimizadas". Por 
lo que sé, los JIT actuales de Python como NUMBA simplemente obvian este 
problema, normalmente suponiendo que no hay excepciones.

-- 
Jesús Cea Avión                         _/_/      _/_/_/        _/_/_/
jcea en jcea.es - https://www.jcea.es/    _/_/    _/_/  _/_/    _/_/  _/_/
Twitter: @jcea                        _/_/    _/_/          _/_/_/_/_/
jabber / xmpp:jcea en jabber.org  _/_/  _/_/    _/_/          _/_/  _/_/
"Things are not so easy"      _/_/  _/_/    _/_/  _/_/    _/_/  _/_/
"My name is Dump, Core Dump"   _/_/_/        _/_/_/      _/_/  _/_/
"El amor es poner tu felicidad en la felicidad de otro" - Leibniz

------------ próxima parte ------------
Se ha borrado un mensaje adjunto que no está en formato texto plano...
Nombre     : OpenPGP_signature
Tipo       : application/pgp-signature
Tamaño     : 495 bytes
Descripción: OpenPGP digital signature
Url        : <https://lists.es.python.org/pipermail/vigo/attachments/20201218/c175996b/attachment.bin>


Más información sobre la lista de distribución Vigo