Esta publicación del creador de Python, Guido Van Rossum, menciona un intento temprano de eliminar el GIL de Python:
Esto se ha intentado antes, con resultados decepcionantes, por lo que soy reacio a esforzarme mucho. En 1999 Greg Stein (¿con Mark Hammond?) Produjo una bifurcación de Python (1.5 creo) que eliminó el GIL, reemplazándolo con bloqueos de grano fino en todas las estructuras de datos mutables. También presentó parches que eliminaron muchas de las dependencias en las estructuras de datos mutables globales, lo cual acepté. Sin embargo, después de la evaluación comparativa, se demostró que incluso en la plataforma con la primitiva de bloqueo más rápida (Windows en ese momento) ralentizó la ejecución de un solo subproceso casi dos veces, lo que significa que en dos CPU, podría obtener un poco más de trabajo hecho sin el GIL que en una sola CPU con el GIL. Esto no fue suficiente, y el parche de Greg desapareció en el olvido. (Ver la reseña de Greg sobre la actuación).
Apenas puedo discutir con los resultados reales, pero realmente me pregunto por qué sucedió esto. Presumiblemente, la razón principal por la que eliminar el GIL de CPython es tan difícil es por el sistema de gestión de memoria de conteo de referencia. Un programa típico de Python llamará Py_INCREF
y Py_DECREF
miles o millones de veces, por lo que es un punto clave de la contención si tuviéramos que envuelva cerraduras alrededor de ella.
Pero, no entiendo por qué agregar primitivas atómicas ralentizaría un solo programa de subprocesos. Supongamos que acabamos de modificar CPython para que la variable de recuento en cada objeto Python fuera una primitiva atómica. Y luego solo hacemos un incremento atómico (instrucciones de buscar y agregar) cuando necesitamos incrementar el recuento de referencia. Esto haría que el recuento de referencias de Python sea seguro para subprocesos, y no debería tener ninguna penalización de rendimiento en una aplicación de un solo subproceso, porque no habría contención de bloqueo.
Pero, por desgracia, muchas personas que son más inteligentes que yo lo han intentado y han fallado, así que obviamente me falta algo aquí. ¿Qué hay de malo en la forma en que estoy viendo este problema?
Respuestas:
No estoy familiarizado con la bifurcación de Greg Stein Python, así que descarte esta comparación como analogía histórica especulativa si lo desea. Pero esta fue exactamente la experiencia histórica de muchas bases de código de infraestructura que pasaron de implementaciones de un solo subproceso a múltiples.
Esencialmente, todas las implementaciones de Unix que estudié en la década de 1990 (AIX, DEC OSF / 1, DG / UX, DYNIX, HP-UX, IRIX, Solaris, SVR4 y SVR4 MP) pasaron exactamente por este tipo de " bloqueo de grano más fino - ¡ahora es más lento! " problema. Los DBMS que seguí (DB2, Ingres, Informix, Oracle y Sybase) también lo pasaron.
He escuchado que "estos cambios no nos retrasarán cuando ejecutamos un solo subproceso" un millón de veces. Nunca funciona de esa manera. El simple acto de verificar condicionalmente "¿estamos ejecutando multiproceso o no?" agrega una sobrecarga real, especialmente en CPU altamente canalizadas. Las operaciones atómicas y los bloqueos de giro ocasionales agregados para garantizar la integridad de las estructuras de datos compartidas deben llamarse con bastante frecuencia, y son muy lentos. Las primitivas de bloqueo / sincronización de primera generación también fueron lentas. La mayoría de los equipos de implementación finalmente agregan varias clases de primitivas, en varias "fortalezas", dependiendo de cuánta protección de enclavamiento se necesita en varios lugares. Luego se dan cuenta de dónde inicialmente abofetearon las primitivas de bloqueo no era realmente el lugar correcto, por lo que tuvieron que perfilar, diseñar alrededor de los cuellos de botella encontrados, y sistemáticamente roto-labrar. Algunos de estos puntos conflictivos eventualmente obtuvieron aceleración del sistema operativo o del hardware, pero toda esa evolución tomó 3-5 años, como mínimo. Mientras tanto, las versiones MP o MT estaban cojeando, en cuanto al rendimiento.
De lo contrario, los equipos de desarrollo sofisticados han argumentado que tales ralentizaciones son básicamente un hecho persistente e intratable de la vida. IBM, por ejemplo, se negó a habilitar AIX para SMP durante al menos 5 años después de la competencia, convencido de que un solo subproceso era simplemente mejor. Sybase utilizó algunos de los mismos argumentos. La única razón por la que algunos de los equipos finalmente llegaron fue porque el rendimiento de un solo hilo ya no podía mejorarse razonablemente a nivel de CPU. Se vieron obligados a usar MP / MT o aceptar tener un producto cada vez menos competitivo.
La concurrencia activa es DURA. Y es engañoso. Todos se apresuran a pensar "esto no será tan malo". Luego golpean las arenas movedizas y tienen que atravesarlo. He visto que esto sucede con al menos una docena de equipos inteligentes de marca reconocida y bien financiados. En general, parecía tomar al menos cinco años después de elegir multihilo para "volver a donde deberían estar, en cuanto al rendimiento" con los productos MP / MT; la mayoría seguía mejorando significativamente la eficiencia / escalabilidad de MP / MT incluso diez años después de realizar el cambio.
Así que mi especulación es que, a falta del respaldo y el apoyo de GvR, nadie ha asumido el largo esfuerzo por Python y su GIL. Incluso si lo hicieran hoy, sería el marco de tiempo de Python 4.x antes de que digas "¡Guau! ¡Realmente hemos superado la joroba MT!"
Quizás haya algo de magia que separe a Python y su tiempo de ejecución de todos los demás softwares de infraestructura con estado: todos los tiempos de ejecución de lenguaje, sistemas operativos, monitores de transacciones y administradores de bases de datos que se han utilizado anteriormente. Pero si es así, es único o casi. Todos los demás que eliminaron un equivalente de GIL han necesitado más de cinco años de esfuerzo e inversión comprometidos para pasar de MT-no a MT-hot.
fuente
Otra hipótesis descabellada: en 1999, Linux y otros Unices no tenían una sincronización performante como ahora
futex(2)
( http://en.wikipedia.org/wiki/Futex ). Esos llegaron alrededor de 2002 (y se fusionaron en 2.6 alrededor de 2004).Como todas las estructuras de datos integradas deben sincronizarse, el bloqueo cuesta mucho. Ӎσᶎ ya señaló, que las operaciones atómicas no son necesariamente baratas.
fuente