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)
fuente
Respuestas:
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".
fuente
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
eval
funció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 queeval
se 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 aeval
. 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.fuente
eval
activando una recompilación y / u optimización.