overlaps chart python ruby compiler-construction

chart - title python matplotlib



¿Por qué se interpreta(python | ruby)? (12)

Bueno, ¿no es una de las fortalezas de estos idiomas que son tan fáciles de escribir? No lo estarían si fueran compilados. Y, por otro lado, los lenguajes dinámicos son más fáciles de interpelar que compilar.

¿Cuáles son las razones técnicas por las que los idiomas como Python y Ruby se interpretan (fuera de la caja) en lugar de compilarlos? Me parece que no debería ser demasiado difícil para las personas con conocimientos en este dominio hacer que estos idiomas no se interpreten como lo son en la actualidad, y veríamos mejoras significativas en el rendimiento. Así que ciertamente me estoy perdiendo algo.


Compilar a Ruby al menos es notoriamente difícil. Estoy trabajando en uno, y como parte de eso escribí una publicación de blog en la que se enumeran algunos de los problemas aquí .

Específicamente, Ruby sufre de un límite muy poco claro (es decir, inexistente) entre la fase de "lectura" y "ejecución" del programa que dificulta la compilación de manera eficiente. Podría simplemente emular lo que hace el intérprete, pero luego no verá mucha velocidad, por lo que no valdrá la pena el esfuerzo. Si quieres compilarlo de manera eficiente, entonces enfrentas muchas complicaciones adicionales para manejar el nivel extremo de dinamismo en Ruby.

La buena noticia es que hay técnicas para superar esto. Self, Smalltalk y Lisp / Scheme''s han tratado con bastante éxito la mayoría de los mismos problemas. Pero se necesita tiempo para analizarlo y descubrir cómo hacerlo funcionar con Ruby. Tampoco ayuda que Ruby tenga una gramática muy complicada.


Creo que la mayor razón para la interpretación de los idiomas es la portabilidad. Como programador, puede escribir código que se ejecutará en un intérprete, no en un sistema operativo específico. Por lo tanto, sus programas se comportan de manera más uniforme en todas las plataformas (más que los lenguajes compilados). Otra ventaja que se me ocurre es que es más fácil tener un sistema de tipo dinámico en un lenguaje interpretado. Creo que los creadores del lenguaje pensaban tener un lenguaje en el que los programadores pueden ser más productivos debido a la gestión automática de la memoria, el sistema de tipo dinámico y las metacampanas de programación sobre cualquier pérdida de rendimiento debida al lenguaje que se interpreta. Si le preocupa el rendimiento, siempre puede compilar el lenguaje a código de máquina nativo empleando una técnica como la compilación JIT.


El esfuerzo requerido para crear un buen compilador para generar código nativo para un nuevo idioma es asombroso . Los pequeños grupos de investigación suelen tardar de 5 a 10 años (ejemplos: SML/NJ , Haskell, Clean, Cecil, lcc , Objective Caml , MLton y muchos otros). Y cuando el lenguaje en cuestión requiere la verificación de tipos y otras decisiones que deben tomarse en el tiempo de ejecución, un compilador debe trabajar mucho más para obtener un buen rendimiento de código nativo (para un excelente ejemplo, vea el trabajo de Craig Chambers y luego Urs Hoelzle en Yo). Las mejoras de rendimiento que puede esperar son más difíciles de realizar de lo que cree. Este fenómeno explica en parte por qué se interpretan tantos lenguajes de tipo dinámico .

Como se ha señalado, un intérprete decente también es instantáneamente portátil, mientras que adaptar compiladores a nuevas arquitecturas de máquinas requiere un esfuerzo sustancial (y es un problema en el que personalmente he estado trabajando durante más de 20 años, con algo de tiempo libre por buen comportamiento). Entonces, un intérprete es una forma de llegar a una amplia audiencia rápidamente.

Finalmente, aunque existen compiladores rápidos e intérpretes lentos, generalmente es más fácil hacer que el ciclo de edición-traducción-go sea más rápido usando un intérprete. (Para algunos buenos ejemplos de compiladores rápidos, vea el lcc mencionado anteriormente, así como el compilador go Ken Thompson. Para un ejemplo de un intérprete relativamente lento, vea GHCi .


El mero reemplazo de un intérprete por un compilador no le proporcionará un aumento de rendimiento tan grande como podría pensar para un lenguaje como Python. Cuando la mayor parte del tiempo se dedica realmente a hacer búsquedas simbólicas de miembros de objetos en diccionarios, no importa si la llamada a la función que realiza dicha búsqueda se interpreta o si es un código de máquina nativo: la diferencia, aunque no del todo insignificante, será pequeña. por consulta de gastos generales.

Para mejorar realmente el rendimiento, necesitas optimizar los compiladores. Y las técnicas de optimización aquí son muy diferentes de las que tiene con C ++, o incluso Java JIT, un compilador optimizado para un lenguaje tipificado dinámicamente / pato como Python necesita hacer una inferencia de tipo muy creativo (incluido el probabilístico, es decir, "90% de probabilidad de ser T "y luego generar un código de máquina eficiente para ese caso con un cheque / bifurcación anterior y un análisis de escape. Esto es duro.


El rendimiento de cálculo sin formato probablemente no sea un objetivo de la mayoría de los idiomas interpretados. Los lenguajes interpretados suelen estar más preocupados por la productividad del programador que por la velocidad bruta. En la mayoría de los casos, estos idiomas son lo suficientemente rápidos para las tareas que los idiomas fueron diseñados para abordar.

Dado eso, y que casi las únicas ventajas de un compilador son la verificación de tipos (difícil de hacer en un lenguaje dinámico) y la velocidad, no hay mucho incentivo para escribir compiladores para la mayoría de los idiomas interpretados.


En un lenguaje compilado, el bucle que se obtiene al crear software es

  1. Hacer un cambio
  2. Compilar cambios
  3. Cambios de prueba
  4. goto 1

Los idiomas interpretados tienden a ser más rápidos para hacer cosas porque puede cortar el paso dos de ese proceso (y cuando se trata de un sistema grande donde los tiempos de compilación pueden ser de más de dos minutos, el paso dos puede agregar una cantidad significativa de hora).

Esta no es necesariamente la razón por la que pensaron los diseñadores de python | ruby, pero tenga en cuenta que "¿con qué eficiencia ejecuta la máquina esto?" Es solo la mitad del problema de desarrollo de software.

También parece que sería más fácil compilar el código en un idioma que se interpreta de forma natural que lo sería agregar un intérprete a un idioma que se compila de forma predeterminada.


Exactamente como (en la implementación típica de) Java o C #, Python se compila primero en algún tipo de código de bytes, dependiendo de la implementación (CPython usa una forma propia especializada, Jython usa JVM como un Java típico, IronPython usa CLR solo como un C # típico, y así sucesivamente) - ese bytecode luego se procesa aún más para ser ejecutado por una máquina virtual (intérprete AKA), que también puede generar un código de máquina "justo a tiempo", conocido como JIT, si y cuando se justifique (Las implementaciones CLR y JVM a menudo lo hacen, la propia máquina virtual de CPython normalmente no lo hace, pero se puede hacer para hacerlo, por ejemplo, con psyco o Unladen Swallow).

JIT puede pagarse por sí mismo por programas de larga duración (si la memoria es más barata que los ciclos de la CPU), pero no (debido a los tiempos de inicio más lentos y al mayor espacio de memoria), especialmente cuando los tipos también deben deducirse o especializarse como parte de la generación del código. Generar código de máquina sin inferencia de tipo o especialización es fácil si eso es lo que quiere, por ejemplo, freeze hace por usted, pero realmente no presenta las ventajas que los "fetichistas de código de máquina" le atribuyen. Por ejemplo, obtienes un binario ejecutable de 1.5 a 2 MB en lugar de un pequeño "hola mundo" .pyc - ¡no tiene mucho sentido! -). Ese ejecutable es independiente y distribuible como tal, pero solo funcionará en un rango muy específico de sistemas operativos y arquitecturas de CPU, por lo que las compensaciones son bastante dudosas en la mayoría de los casos. Y, el tiempo que lleva preparar el ejecutable es bastante largo, por lo que sería una elección loca hacer que ese modo de operación sea el predeterminado.


Hoy en día, ya no existe una distinción fuerte entre las lenguas "compiladas" e "interpretadas". De hecho, Python se compila tanto como Java, las únicas diferencias son:

  • El compilador de Python es mucho más rápido que el compilador de Java.
  • Python compila automáticamente el código fuente a medida que se ejecuta, no se requiere un paso de "compilación" separado
  • El bytecode de Python es diferente del bytecode JVM

Python incluso tiene una función llamada compile() que es una interfaz para el compilador.

Parece que la distinción que está haciendo es entre los idiomas "tipificados dinámicamente" y "tipificados estáticamente". En lenguajes dinámicos como Python, puedes escribir código como:

def fn(x, y): return x.foo(y)

Tenga en cuenta que los tipos de x y y no están especificados. En el tiempo de ejecución, esta función mirará a x para ver si tiene una función miembro llamada foo , y si es así, la llamará con y . Si no, lanzará un error de tiempo de ejecución que indica que no se encontró tal función. Este tipo de búsqueda en tiempo de ejecución es mucho más fácil de representar utilizando una representación intermedia como bytecode, donde una máquina virtual en tiempo de ejecución realiza la búsqueda en lugar de tener que generar código de máquina para realizar la búsqueda en sí misma (o, llame a una función para hacer la búsqueda, que es lo que bytecode hará de todos modos).

Python tiene proyectos como Psyco , PyPy y Unladen Swallow que toman varios enfoques para compilar el código objeto de Python en algo más cercano al código nativo. Hay una investigación activa en esta área, pero no hay (hasta ahora) una respuesta simple.


Muchas rasones:

  • bucle de desarrollo más rápido, prueba de escritura frente a prueba de escritura de compilación
  • más fácil de organizar para el comportamiento dinámico (reflexión, metaprogramación)
  • hace que todo el sistema sea portátil (solo vuelva a compilar el código C subyacente y estará listo para usar una nueva plataforma)

Piense en lo que pasaría si el sistema no fuera interpretado. Digamos que usaste la traducción a C como el mecanismo. El código compilado tendría que verificar periódicamente si había sido reemplazado por la metaprogramación. Una situación similar surge con las funciones eval() -type. En esos casos, tendría que ejecutar el compilador de nuevo, un proceso escandalosamente lento, o también tendría que tener al intérprete en funcionamiento en cualquier momento.

La única alternativa aquí es un compilador JIT. Estos sistemas son altamente complejos y sofisticados y tienen huellas de tiempo de ejecución aún mayores que todas las otras alternativas. Se inician muy lentamente, lo que los hace poco prácticos para las secuencias de comandos. ¿Alguna vez has visto un script de Java? Yo no tengo

Entonces, tienes dos opciones:

  • Todas las desventajas de un compilador y un intérprete
  • sólo las desventajas de un intérprete

No es sorprendente que, en general, la implementación primaria solo vaya con la segunda opción. Es muy posible que algún día podamos ver implementaciones secundarias como compiladores. Ruby 1.9 y Python tienen un bytecode VM''s; esos son la mitad de camino. Un compilador puede apuntar solo a un código no dinámico, o puede tener varios niveles de soporte de idioma declarables como opciones. Pero como tal cosa no puede ser la implementación principal, representa mucho trabajo para un beneficio muy marginal. Ruby ya tiene 200,000 líneas de C en ella ...

Supongo que debo agregar que siempre se puede agregar una extensión compilada en C (o, con algún esfuerzo, cualquier otro idioma). Entonces, digamos que tienes una operación numérica lenta. Si agrega, diga Array#newOp con una implementación en C, luego obtiene la aceleración, el programa permanece en Ruby (o lo que sea) y su entorno obtiene un nuevo método de instancia. ¡Todos ganan! Esto reduce la necesidad de una implementación secundaria problemática.


Por diseño.

Los autores querían algo donde poder escribir guiones.

Python se compila la primera vez que se ejecuta, aunque


REPL . No lo golpees hasta que lo hayas probado. :)