¿Qué características semánticas de Python (y otros lenguajes dinámicos) contribuyen a su lentitud?

26

No sé muy bien Python. Estoy tratando de comprender con mayor precisión qué características exactas de los lenguajes dinámicos (a la Python, Lua, Scheme, Perl, Ruby, ...) están obligando a que sus implementaciones sean lentas.

Como ejemplo, la maquinaria metatable de Lua 5.3 intuitivamente haría a Lua bastante lenta, pero en la práctica se rumorea que Lua es bastante rápido (y más rápido que Python).

Además, tengo la intuición (quizás una equivocada) de que, dado que en los procesadores actuales la memoria es mucho más lenta que el cómputo sin procesar (un acceso a la memoria con una falta de caché necesita el mismo tiempo que cientos de operaciones aritméticas), la verificación dinámica de tipos (à la if (value->type != INTEGER_TAG) return;in Lenguaje C) podría correr bastante rápido.

Por supuesto, el análisis de todo el programa (como lo está haciendo la implementación de Stalin Scheme ) puede hacer una implementación dinámica del lenguaje ya que un traductor se ejecuta rápidamente, pero supongamos que al principio no tengo tiempo para diseñar un analizador de programa completo.

(Estoy diseñando un lenguaje dinámico en mi monitor MELT , y parte de él se traduciría a C)

Basile Starynkevitch
fuente
1
Consejos de rendimiento de Lua , que explican por qué algunos programas de Lua son lentos y cómo solucionarlos.
Robert Harvey

Respuestas:

24

¿Qué características semánticas de Python (y otros lenguajes dinámicos) contribuyen a su lentitud?

Ninguna.

El rendimiento de las implementaciones de lenguaje es una función del dinero, los recursos y las tesis doctorales, no las características del lenguaje. Self es mucho más dinámico que Smalltalk y un poco más dinámico que Python, Ruby, ECMAScript o Lua, y tenía una VM que superó a todas las máquinas virtuales Lisp y Smalltalk existentes (de hecho, la distribución Self se envió con un pequeño intérprete Smalltalk escrito en Self , e incluso eso fue más rápido que la mayoría de las máquinas virtuales Smalltalk existentes, y fue competitivo y, a veces, incluso más rápido que las implementaciones de C ++ de la época.

Luego, Sun dejó de financiar Self e IBM, Microsoft, Intel y Co. comenzaron a financiar C ++, y la tendencia se invirtió. Los desarrolladores de Self dejaron Sun para comenzar su propia compañía, donde usaron la tecnología desarrollada para Self VM para construir una de las máquinas virtuales Smalltalk más rápidas de la historia (la VM Animorphic), y luego Sun volvió a comprar esa compañía y una versión ligeramente modificada de que Smalltalk VM ahora se conoce mejor con el nombre de "HotSpot JVM". Irónicamente, los programadores de Java desprecian los lenguajes dinámicos por ser "lentos", cuando de hecho, Javafue lento hasta que adoptó la tecnología del lenguaje dinámico. (Sí, es cierto: el HotSpot JVM es esencialmente una VM Smalltalk. El verificador de bytecode realiza muchas verificaciones de tipo, pero una vez que el verificador acepta el bytecode, la VM, y especialmente el optimizador y el JIT, en realidad no lo hacen mucho interés con los tipos estáticos!)

CPython simplemente no hace muchas de las cosas que hacen que los lenguajes dinámicos (o más bien el despacho dinámico) sean rápidos: compilación dinámica (JIT), optimización dinámica, línea especulativa, optimización adaptativa, des-optimización dinámica, retroalimentación / inferencia de tipo dinámico. También existe el problema de que casi toda la biblioteca básica y estándar está escrita en C, lo que significa que incluso si hace que Python 100x sea más rápido de repente, no le ayudará mucho, porque algo como el 95% del código ejecutado por un El programa Python es C, no Python. Si todo se escribiera en Python, incluso las aceleraciones moderadas crearían un efecto de avalancha, donde los algoritmos se vuelven más rápidos y las estructuras de datos centrales se hacen más rápidas, pero, por supuesto, las estructuras de datos centrales también se usan dentro de los algoritmos, y los algoritmos centrales y los datos centrales las estructuras se usan en todas partes,

Hay un par de cosas que son notoriamente malas para los lenguajes OO gestionados por memoria (dinámicos o no) en los sistemas actuales. La memoria virtual y la protección de memoria pueden ser un factor decisivo para el rendimiento de la recolección de basura en particular, y el rendimiento del sistema en general. Y es completamente innecesario en un lenguaje seguro para la memoria: ¿por qué protegerse contra accesos ilegales a la memoria cuando no hay ningún acceso a la memoria en el idioma para empezar? Azul ha descubierto usar MMU potentes y modernos (Intel Nehalem y más nuevos, y el equivalente de AMD) para ayudar a la recolección de basura en lugar de obstaculizarlo, pero a pesar de que es compatible con la CPU, los subsistemas de memoria actuales de los sistemas operativos principales no son lo suficientemente potentes para permitir esto (es por eso que JVM de Azul realmente se ejecuta virtualizado en el metal desnudo además el sistema operativo, no dentro de él).

En el proyecto Singularity OS, Microsoft ha medido un impacto de ~ 30% en el rendimiento del sistema al usar la protección MMU en lugar del sistema de tipos para la separación de procesos.

Otra cosa que Azul notó al construir sus CPU Java especializadas fue que las CPU convencionales modernas se centran en algo completamente incorrecto cuando intentan reducir el costo de las fallas de caché: intentan reducir la cantidad de fallas de caché a través de cosas como la predicción de ramificaciones, la captación previa de memoria, y así. Pero, en un programa OO altamente polimórfico, los patrones de acceso son básicamente pseudoaleatorios, simplemente no hay nada que predecir. Por lo tanto, todos esos transistores simplemente se desperdician, y lo que se debe hacer es reducir el costo de cada pérdida de caché individual. (El costo total es el costo de #misses *, la corriente principal intenta derribar el primero, Azul el segundo). Los Aceleradores de Computación Java de Azul podrían tener 20000 fallas concurrentes de caché en vuelo y aún avanzar.

Cuando comenzó Azul, pensaron que tomarían algunos componentes de E / S listos para usar y diseñarían su propio núcleo de CPU especializado, pero lo que realmente necesitaron hacer fue exactamente lo contrario: tomaron un estándar bastante estándar. plataforma RISC de 3 direcciones y diseñó su propio controlador de memoria, MMU y subsistema de caché.

tl; dr : La "lentitud" de Python no es una propiedad del lenguaje, sino a) su implementación ingenua (primaria), yb) el hecho de que las CPU y los SO modernos están específicamente diseñados para hacer que C funcione rápidamente, y las características que have for C no están ayudando (caché) o incluso perjudicando activamente (memoria virtual) el rendimiento de Python.

Y aquí puede insertar prácticamente cualquier lenguaje administrado por memoria con polimorfismo dinámico ad-hoc ... cuando se trata de los desafíos de una implementación eficiente, incluso Python y Java son prácticamente "el mismo lenguaje".

Jörg W Mittag
fuente
¿Tiene un enlace o referencia para Azul?
Basile Starynkevitch
44
No estoy de acuerdo con que la semántica de un lenguaje no tenga ningún efecto sobre su capacidad para implementarse de manera eficiente. Sí, una buena implementación JIT con optimizaciones y análisis de primera clase puede mejorar enormemente el rendimiento de un lenguaje, pero al final hay ciertos aspectos de la semántica que inevitablemente terminarán siendo cuellos de botella. Ya sea el requisito de C para el alias estricto de punteros o el requisito de Python de que las operaciones de lista se realicen atómicamente, hay ciertas decisiones semánticas que inevitablemente terminan perjudicando el rendimiento de algunas aplicaciones.
Julio
1
Como comentario aparte ... ¿tiene una referencia para esa mejora del 30% para Singularity? He sido un defensor de los sistemas operativos de protección basada en el lenguaje durante muchos años, pero nunca he visto esa cifra antes, y me parece bastante sorprendente (las cifras que he visto en el pasado han estado más cerca del 10%) y me pregunto qué hicieron para obtener esa mejora ...
Jules
55
@MasonWheeler: Porque solo hay implementaciones de Python desagradables por ahí. Ningún implementador de Python ha gastado ni una pequeña fracción del dinero, la gente, la investigación y los recursos que IBM, Sun, Oracle, Google y Co. han gastado en J9, JRockit, HotSpot y Co. Las 5 implementaciones de Python combinadas probablemente ni siquiera tener la mano de obra que Oracle está gastando solo en el recolector de basura. IBM está trabajando en una implementación de Python basada en Eclipse OMR (el marco VM de código abierto componente extraído de J9), estoy dispuesto a apostar a que su rendimiento estará dentro de un orden de magnitud de J9
Jörg W Mittag
2
Para el registro, C es lento en comparación con Fortran para el trabajo numérico, ya que Fortran aplica un alias estricto para que el optimizador pueda ser más agresivo.
Michael Shopsin
8

Si bien la implementación actual de Python (que carece de muchas de las optimizaciones realizadas por otros lenguajes dinámicos, por ejemplo, implementaciones modernas de Javascript y, como usted señala, Lua) es la fuente de la mayoría de sus problemas, tiene algunos problemas semánticos que lo harían Es difícil que una implementación compita con otros idiomas, al menos en ciertos campos. Algunos que vale la pena considerar especialmente:

  • La definición del lenguaje requiere que las operaciones de lista y diccionario sean atómicas. Esto significa que, a menos que un compilador JIT pueda probar que ninguna referencia a un objeto de lista ha escapado de su hilo actual (un análisis que es difícil en muchos casos e imposible en el caso general), debe garantizar que el acceso al objeto esté serializado (p. Ej. mediante bloqueo). La implementación de CPython resuelve este problema utilizando el notorio "bloqueo global del intérprete" que evita que el código Python se use efectivamente en entornos de multiprocesamiento con técnicas de subprocesos múltiples, y aunque son posibles otras soluciones, todas tienen problemas de rendimiento.

  • Python no tiene ningún mecanismo para especificar el uso de objetos de valor; todo se maneja por referencia, agregando indirección adicional donde no se requiere necesariamente. Si bien es posible que un compilador JIT infiera objetos de valor en algunos casos y lo optimice automáticamente, no es posible hacerlo en general y, por lo tanto, un código que no está escrito cuidadosamente para garantizar que la optimización sea posible (que es algo así como un arte negro) sufrirá.

  • Python tiene una evalfunción, lo que significa que un compilador JIT no puede hacer suposiciones sobre acciones que no ocurren, incluso si realiza un análisis de todo el programa, siempre que evalse use una vez. Por ejemplo, un compilador de Python no puede suponer que una clase no tiene subclases y, por lo tanto, desvirtualiza las llamadas a métodos, porque esa suposición podría negarse más tarde mediante una llamada a eval. En su lugar, debe realizar verificaciones de tipo dinámico para garantizar que las suposiciones hechas por el código compilado nativo no se hayan invalidado antes de la ejecución de ese código.

Jules
fuente
3
El último punto puede mitigarse evalactivando una recompilación y / u optimización.
Jörg W Mittag
44
Por cierto, eso tampoco es exclusivo de Python. Java (o más bien la JVM) tiene carga de código dinámico y enlace dinámico, por lo que el Análisis de Jerarquía de Clase es equivalente a resolver el Problema de detención allí también. Sin embargo, HotSpot felizmente alinea especulativamente los métodos polimórficos, y si algo en la jerarquía de clases cambia, bueno, simplemente los desconectará nuevamente.
Jörg W Mittag