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 _/_/ _/_/_/ _/_/_/ j...@jcea.es - https://www.jcea.es/ _/_/ _/_/ _/_/ _/_/ _/_/ Twitter: @jcea _/_/ _/_/ _/_/_/_/_/ jabber / xmpp:j...@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
OpenPGP_signature
Description: OpenPGP digital signature
_______________________________________________ Python-es mailing list Python-es@python.org https://mail.python.org/mailman/listinfo/python-es