oop - tabla - ¿Por qué casi todos los lenguajes OO se compilan en bytecode?
tabla de codigo bytecode (8)
Bytecode es un medio significativamente más flexible que el código de máquina. Primero, proporciona la base para la portabilidad de la plataforma sin la necesidad de un compilador o código fuente de envío. Por lo tanto, un desarrollador puede distribuir una única versión de la aplicación sin tener que renunciar a la fuente, requerir herramientas de desarrollador complejas o anticipar posibles plataformas de destino. Mientras que lo posterior no siempre es práctico, sucede. Especialmente con bibliotecas de desarrolladores, digo que distribuyo una biblioteca que solo he probado en Windows, pero alguien más la usa en Linux o Android. Sucede con bastante frecuencia en realidad, y la mayoría de las veces funciona como se espera.
El código de bytes generalmente también está más optimizado que un intérprete porque está más cerca de las instrucciones de la máquina, por lo que es más rápido de traducir a las instrucciones de la máquina. No todos los lenguajes OO están compilados. Ruby, Python e incluso Javascript se interpretan de modo que no se compilan para nada, por lo que el intérprete de ruby tiene que tomar un lenguaje muy flexible y convertir eso en instrucciones, pero esa flexibilidad tiene un precio pagado en tiempo de ejecución: analizar texto, generar AST , convertir AST a código de máquina, etc. También es fácil realizar optimizaciones como JIT, donde el código de byte se traduce directamente al código de máquina, e incluso ofrece la posibilidad de crear optimizaciones para hardware específico.
Finalmente, solo porque un idioma compila a bytecode no impide que otros lenguajes aprovechen ese código de byte. Ahora, cualquier optimización que use ese código de bytes se puede aplicar a estos otros idiomas que pueden saber cómo traducirse ellos mismos a ese código de bytes. Eso hace que el código de bytes sea una capa muy importante para la reutilización de otros idiomas.
La compilación de código OO y byte se remonta a los 70 con Smalltalk, y estoy seguro de que alguien dirá LISP desde los 50/60. Pero, en realidad no fue hasta los años 90 cuando comenzó a usarse realmente en sistemas de producción a gran escala.
La compilación nativa suena como la ruta óptima, y probablemente la razón por la que nuestra industria pasó 20 años o más pensando que fue LA RESPUESTA a todos nuestros problemas, pero en los últimos 15 años hemos visto que la compilación de códigos de byte toma el escenario y ha sido una ventaja significativa sobre lo que lo hicimos antes Mirando hacia atrás nos damos cuenta de cuánto tiempo se pierde compilando de forma nativa todo, principalmente a mano.
De los lenguajes orientados a objetos que conozco, casi todos, excepto C ++ y Objective-C, se compilan en un código de bytes que se ejecuta en algún tipo de máquina virtual. ¿Por qué se han decidido tantos idiomas diferentes al compilar en bytecode, en lugar de en código de máquina? ¿Es posible en princible tener un lenguaje OOP gestionado por memoria de alto nivel que se compile a código de máquina?
Edición: Soy consciente de que el soporte multiplataforma a menudo se avanza como una ventaja de este enfoque. Sin embargo, es bastante posible compilar de forma nativa en múltiples plataformas, sin hacer un nuevo compilador por plataforma. Uno puede, por ejemplo, emitir código C y luego compilarlo con GCC.
Como otro punto de datos, el lenguaje de programación D tiene GC, OO y un nivel mucho más alto que C ++ mientras se está compilando en código nativo.
Es más fácil desarrollar un intérprete que un compilador.
Esfuerzo en el desarrollo de ...:
intérprete <bytecode-interpreter <bytecode-jit-compiler <compilador-a-plataforma-lenguaje <compilador-a-máquina-múltiple-dependiente-ensamblador.
Es una tendencia general detener el desarrollo en jit-compilers debido a la independencia de la plataforma. Solo los idiomas preferidos con respecto al rendimiento y la investigación en informática teórica son y se desarrollarán en TODAS las direcciones posibles, incluido el nuevo intérprete de códigos de bytes, incluso si existen compiladores avanzados y buenos para lenguajes independientes de la plataforma y diferentes ensambladores dependientes de la máquina.
La investigación en lenguajes OOP es bonita ... digamos aburrida, en comparación con los lenguajes funcionales, porque las tecnologías de compilador y lenguaje realmente nuevas se expresan más fácilmente con / in / utilizando la teoría de la categoría matemática y las descripciones matemáticas de los sistemas de tipo de viaje completo. En otras palabras: es casi funcional en sí mismo, mientras que los lenguajes imperativos son casi solo ensamblajes con un poco de azúcar sintáctica. Los lenguajes OOP tienden a ser lenguajes imperativos, porque los lenguajes funcionales ya tienen cierres y lambda. Hay otras formas de implementar "interfaces" tipo java en lenguajes funcionales, y simplemente no hay necesidad de características orientadas a objetos adicionales.
En es decir, Haskell, agregar la característica de la programación similar a OOP probablemente sea más que solo unos pocos pasos atrás en tecnología, no tendría sentido usarla. (<- ¿no es solo IMHO ... alguna vez escuchó sobre GADTs o clases de tipo de múltiples parámetros?) Probablemente podría haber incluso mejores formas de crear dinámicamente Objetos con Interfaces para comunicarse con lenguajes OOP que cambiando ese mismo idioma . Pero también hay otros lenguajes funcionales que combinan explícitamente los aspectos funcionales y OOP. Solo hay más ciencia con lenguajes principalmente funcionales que lenguajes OO no funcionales.
Los idiomas OO no se pueden compilar fácilmente en otros idiomas OO, si son de alguna manera más "avanzados". Por lo general, tienen funciones como protector de pila, capacidades avanzadas de depuración, subprocesos múltiples abstractos e inspeccionables, carga dinámica de objetos desde archivos de Internet ... Muchas de estas funciones no son fáciles de realizar con C o C ++ como compilador -respuesta El lenguaje funcional LISP (¡que tiene 50 años!) Fue AFAIK el primero con el recolector de basura. Como el LISP del back-end del compilador usó una versión pirateada del lenguaje C, porque la C simple no permitía algunas de esas cosas, el ensamblador sí lo permitía, es decir, las llamadas de cola propiamente dicha o las tablas de código siguiente. C-- permite eso.
Otro aspecto: los lenguajes imperativos están diseñados para ejecutarse en una arquitectura específica, es decir, los programas C y C ++ se ejecutan solo en esas arquitecturas, están programados para. Java es más extremo: solo se ejecuta en una única arquitectura, una virtual, que a su vez se ejecuta en otras. Los lenguajes funcionales son, por su diseño, bastante independientes de la arquitectura: LISP fue desarrollado para ser tan inmenso en cuanto a la arquitectura, que podría compilarse en un código genético, en un futuro lejano. Sí, como programas que se ejecutan en células biológicas vivas.
Con el código de bytes para el LLVM, lo más probable es que los lenguajes funcionales se compilen también en el futuro. Lo más probable es que la mayoría de los lenguajes imperativos aún tengan los mismos problemas heredados que ahora no tienen que abstraerse lo suficiente. Bueno, no estoy tan seguro acerca de clang y D, pero esos dos no son "los más" de todos modos.
Esto se hace para permitir a un compilador de VM o JIT la posibilidad de compilar el código a pedido de manera óptima para la arquitectura en la que se ejecuta el código. Además, permite que el código de bytes multiplataforma se cree una vez y luego se ejecute en múltiples arquitecturas de hardware. Esto permite colocar optimizaciones específicas de hardware en el código compilado.
Dado que el código de bytes no se limita a una microarquitectura, puede ser más pequeño que el código de máquina. Las instrucciones complejas se pueden representar frente a las instrucciones mucho más primitivas disponibles en las CPU de hoy en día, ya que las restricciones en el diseño de las instrucciones de la CPU son muy diferentes de las restricciones en el diseño de una arquitectura de código de bytes.
Luego está el tema de la seguridad. El bytecode puede verificarse y analizarse antes de la ejecución (es decir, no hay desbordamientos de búfer, las variables de cierto tipo a las que se accede como algo que no son), etc.
Estoy de acuerdo con la respuesta de Chubbard y agregaría que en los idiomas OO la información de tipo puede ser muy importante para habilitar optimizaciones por máquinas virtuales o compiladores de último nivel
Java usa bytecode porque dos de sus objetivos de diseño iniciales eran la portabilidad y la compacidad. Ambos procedían de la visión inicial de un lenguaje para dispositivos integrados, donde se podían descargar fragmentos de código sobre la marcha.
Python, Ruby, Smalltalk, javascript, awk, etc. utilizan el bytecode porque escribir un compilador nativo es mucho trabajo, pero un intérprete textual es demasiado lento: el bytecode es bastante fácil de escribir, pero también es bastante rápido de escribir. correr.
No tengo idea de por qué los lenguajes de Microsoft usan el código de bytes, ya que para ellos, ni la portabilidad ni la compacidad son un gran problema. Gran parte del pensamiento detrás del CLR surgió de científicos informáticos en Cambridge, así que me imagino que consideraciones como la facilidad de análisis y verificación del programa estaban involucradas.
Tenga en cuenta que además de C ++ y Objective C, Eiffel, Ada 9X, Vala y Go son lenguajes OO (de varias versiones) que se compilan directamente en el código nativo.
En general, diría que OO y el código de bytes no van de la mano. Más bien, tenemos una convergencia coincidente de varias corrientes de desarrollo: los intérpretes tradicionales de los lenguajes de script como Python y Ruby, el plan maestro de Java de Gosling, y cualquiera que sean los motivos de Microsoft.
La razón principal por la que la mayoría de los lenguajes interpretados (no específicamente los lenguajes OO) se compilan a bytecode es para el rendimiento. La parte más costosa de interpretar el código es transformar el origen del texto en una representación intermedia. Por ejemplo, para realizar algo como:
foo + bar;
El intérprete tendría que escanear 10 caracteres, transformarlos en 4 tokens, crear un AST para la operación, resolver tres símbolos (+ es un símbolo, que depende de los tipos de foo y barra), todo antes de que pueda realizar cualquier acción que En realidad depende del estado de ejecución del programa. Nada de esto puede cambiar de una ejecución a otra, y muchos idiomas intentan almacenar alguna forma de representación intermedia.
Bytecode, en lugar de almacenar un AST tiene algunas ventajas. Por un lado, los códigos de bytes son fáciles de serializar, por lo que el IR puede escribirse en el disco y reutilizarse en la siguiente invocación, lo que reduce aún más el tiempo de interpretación. Otra razón es que el código de bytes a menudo ocupa menos memoria RAM real. las representaciones de bytecode son a menudo fáciles de compilar justo a tiempo, porque a menudo son estructuralmente similares al código de máquina típico.
No hay razón, de hecho, esto es una especie de coincidencia. OOP ahora es el concepto líder en programación "grande", y también lo son las máquinas virtuales.
También tenga en cuenta que hay dos partes distintas de las máquinas virtuales tradicionales: el recolector de basura y el intérprete de código de bytes / compilador JIT , y estas partes pueden existir por separado. Por ejemplo, la implementación de Common Lisp llamada SBCL compila el programa en un código nativo, pero en tiempo de ejecución utiliza en gran medida la recolección de basura.