Idiomas compilados vs. interpretados

284

Estoy tratando de comprender mejor la diferencia. He encontrado muchas explicaciones en línea, pero tienden a las diferencias abstractas más que a las implicaciones prácticas.

La mayoría de mis experiencias de programación han sido con CPython (dinámico, interpretado) y Java (estático, compilado). Sin embargo, entiendo que hay otros tipos de idiomas interpretados y compilados. Además del hecho de que los archivos ejecutables se pueden distribuir desde programas escritos en lenguajes compilados, ¿existen ventajas / desventajas para cada tipo? A menudo, escucho a personas argumentar que los lenguajes interpretados se pueden usar de manera interactiva, pero creo que los lenguajes compilados también pueden tener implementaciones interactivas, ¿correcto?

quimeracoder
fuente
32
Elegiste exactamente los peores idiomas para esta comparación. Ambos están compilados por bytes. La única diferencia real entre ellos es el JITer, e incluso Python tiene uno parcial (psico).
Ignacio Vazquez-Abrams
1
Un buen ejemplo de un lenguaje compilado interactivo es Clojure: todo está completamente compilado (primero en JVM y luego en código nativo a través de JIT). Sin embargo, gran parte de la compilación ocurre dinámicamente, y el desarrollo a menudo se realiza en un shell REPL interactivo donde puede evaluar cualquier función que desee en el entorno de ejecución.
mikera
Standard ML es otro lenguaje compilado interactivo; el compilador incorporado también emite código de máquina nativa real.
Donal Fellows

Respuestas:

459

Un lenguaje compilado es aquel en el que el programa, una vez compilado, se expresa en las instrucciones de la máquina de destino. Por ejemplo, una operación adicional "+" en su código fuente podría traducirse directamente a la instrucción "AGREGAR" en el código de máquina.

Un lenguaje interpretado es aquel en el que las instrucciones no son ejecutadas directamente por la máquina de destino, sino que son leídas y ejecutadas por algún otro programa (que normalmente está escrito en el idioma de la máquina nativa). Por ejemplo, el intérprete reconocería la misma operación "+" en tiempo de ejecución, que luego llamaría a su propia función "add (a, b)" con los argumentos apropiados, que luego ejecutarían la instrucción "ADD" del código de máquina .

Puede hacer cualquier cosa que pueda hacer en un idioma interpretado en un idioma compilado y viceversa, ambos están completos. Sin embargo, ambos tienen ventajas y desventajas para su implementación y uso.

Voy a generalizar completamente (¡los puristas me perdonan!) Pero, a grandes rasgos, aquí están las ventajas de los lenguajes compilados:

  • Rendimiento más rápido al usar directamente el código nativo de la máquina de destino
  • Oportunidad de aplicar optimizaciones bastante potentes durante la etapa de compilación

Y aquí están las ventajas de los idiomas interpretados:

  • Más fácil de implementar (¡escribir buenos compiladores es muy difícil!)
  • No es necesario ejecutar una etapa de compilación: puede ejecutar código directamente "sobre la marcha"
  • Puede ser más conveniente para lenguajes dinámicos.

Tenga en cuenta que las técnicas modernas, como la compilación de código de bytes, agregan cierta complejidad adicional: lo que sucede aquí es que el compilador apunta a una "máquina virtual" que no es lo mismo que el hardware subyacente. Estas instrucciones de máquina virtual pueden compilarse nuevamente en una etapa posterior para obtener código nativo (por ejemplo, como lo hizo el compilador JVM JIT de Java).

mikera
fuente
1
No todos los lenguajes compilados necesitan una etapa de compilación lenta. Las implementaciones serias de Common Lisp son compiladores, y a menudo no se molestan con un intérprete, prefieren compilar realmente rápido sobre la marcha. Por otro lado, Java necesita un paso de compilación, y generalmente es visible.
David Thornley
2
@Kareem: el compilador JIT solo hace 1) y 2) una vez , después de eso es código nativo hasta el final. El intérprete debe hacer tanto 1) como 2) cada vez que se llama al código (que puede ser muchas, muchas veces ...). Entonces, con el tiempo, el compilador JIT gana por un largo margen.
mikera
3
Sí, el bytecode se traduce al código de máquina en algún momento durante la ejecución general del programa (a diferencia de antes de la ejecución del programa, como es el caso de un compilador tradicional). Pero un fragmento de código dado podría ejecutarse más de 10 millones de veces durante la ejecución general del programa. (Probablemente) solo se compila una vez del código de bytes al código de máquina. Por lo tanto, la sobrecarga de tiempo de ejecución de JIT es pequeña y puede ignorarse para los programas de larga ejecución. Una vez que el compilador JIT haya terminado de hacer su trabajo, efectivamente estará ejecutando código de máquina puro todo el camino.
mikera
2
Esto es en realidad una falsa dicotomía. No hay nada intrínseco en un lenguaje que haga que compile nuestro interpretado. No es más que un concepto erróneo ampliamente difundido. Muchos idiomas tienen ambas implementaciones y todos los idiomas pueden tener cualquiera.
mmachenry
2
@mmachenry no es una falsa dicotomía. El "lenguaje de programación" incluye tanto el diseño como la implementación. Mientras que, en un sentido teórico, una definición de lenguaje dada puede compilarse e interpretarse, en la práctica del mundo real existen diferencias considerables en la implementación. Nadie ha resuelto cómo compilar efectivamente ciertas construcciones de lenguaje, por ejemplo, es un problema de investigación abierto.
mikera
99

Un lenguaje en sí no se compila ni se interpreta, solo una implementación específica de un lenguaje. Java es un ejemplo perfecto. Hay una plataforma basada en bytecode (JVM), un compilador nativo (gcj) y un intérprete para un superconjunto de Java (bsh). Entonces, ¿qué es Java ahora? Bytecode-compilado, nativo-compilado o interpretado?

Otros idiomas, que se compilan e interpretan, son Scala, Haskell u Ocaml. Cada uno de estos idiomas tiene un intérprete interactivo, así como un compilador para código de bytes o código de máquina nativo.

Por lo tanto, en general, clasificar los idiomas por "compilado" e "interpretado" no tiene mucho sentido.

Lunaryorn
fuente
3
Estoy de acuerdo. O digamos: hay compiladores nativos (que crean código de máquina para que la CPU los coma), y compiladores no tan nativos (que crean material tokenizado, es decir, código intermedio, que algún compilador justo a tiempo compila en código de máquina antes ( o durante el tiempo de ejecución (UNA VEZ), y hay compiladores "reales" que nunca producen código de máquina y nunca dejan que la CPU ejecute el código. Estos últimos son intérpretes. Hoy en día, los compiladores nativos que producen directamente código de máquina (CPU) en tiempo de compilación son cada vez más raros. Delphi / Codegear es uno de los mejores sobrevivientes.
TheBlastOne
57

Comienza a pensar en términos de: explosión del pasado

Érase una vez, hace mucho, mucho tiempo, vivía en la tierra de los intérpretes y compiladores informáticos. Todo tipo de alboroto se produjo por los méritos de uno sobre el otro. La opinión general en ese momento era algo similar a:

  • Intérprete: Rápido de desarrollar (editar y ejecutar). Lento de ejecutar porque cada declaración tenía que ser interpretada en código máquina cada vez que se ejecutaba (piense en lo que esto significaba para un bucle ejecutado miles de veces).
  • Compilador: Desarrollo lento (editar, compilar, enlazar y ejecutar. Los pasos de compilación / enlace pueden tomar mucho tiempo). Rápido de ejecutar. Todo el programa ya estaba en código máquina nativo.

Existía una diferencia de uno o dos órdenes de magnitud en el rendimiento del tiempo de ejecución entre un programa interpretado y un programa compilado. Otros puntos distintivos, por ejemplo, la mutabilidad en tiempo de ejecución del código, también fueron de interés, pero la principal distinción giraba en torno a los problemas de rendimiento en tiempo de ejecución.

Hoy el paisaje ha evolucionado hasta tal punto que la distinción compilada / interpretada es prácticamente irrelevante. Muchos idiomas compilados recurren a servicios de tiempo de ejecución que no están completamente basados ​​en código de máquina. Además, la mayoría de los lenguajes interpretados se "compilan" en código de bytes antes de la ejecución. Los intérpretes de código de bytes pueden ser muy eficientes y rivalizar con algunos códigos generados por el compilador desde el punto de vista de la velocidad de ejecución.

La diferencia clásica es que los compiladores generan código máquina nativo, los intérpretes leen el código fuente y el código máquina generado sobre la marcha utilizando algún tipo de sistema de tiempo de ejecución. Hoy en día quedan muy pocos intérpretes clásicos, casi todos compilan en código de bytes (o algún otro estado semi-compilado) que luego se ejecuta en una "máquina" virtual.

NealB
fuente
1
Agradable - gran resumen en el último párrafo - ¡gracias!
ckib16
26

Los casos extremos y simples:

  • Un compilador producirá un ejecutable binario en el formato ejecutable nativo de la máquina de destino. Este archivo binario contiene todos los recursos necesarios, excepto las bibliotecas del sistema; está listo para ejecutarse sin más preparación y procesamiento, y funciona como un rayo porque el código es el código nativo de la CPU en la máquina de destino.

  • Un intérprete presentará al usuario un mensaje en un bucle donde puede ingresar sentencias o código, y al presionar RUNo el equivalente, el intérprete examinará, escaneará, analizará y ejecutará interpretativamente cada línea hasta que el programa llegue a un punto de detención o un error . Debido a que cada línea se trata por sí sola y el intérprete no "aprende" nada de haber visto la línea antes, el esfuerzo de convertir el lenguaje legible por humanos en instrucciones automáticas se incurre cada vez por cada línea, por lo que es lento. En el lado positivo, el usuario puede inspeccionar e interactuar de otro modo con su programa de todo tipo de formas: cambiando variables, cambiando código, ejecutándose en modos de rastreo o depuración ... lo que sea.

Con eso fuera del camino, déjame explicarte que la vida ya no es tan simple. Por ejemplo,

  • Muchos intérpretes precompilarán el código que reciben para que el paso de traducción no tenga que repetirse una y otra vez.
  • Algunos compiladores compilan no según las instrucciones de la máquina específicas de la CPU, sino por código de bytes, una especie de código de máquina artificial para una máquina ficticia. Esto hace que el programa compilado sea un poco más portátil, pero requiere un intérprete de código de bytes en cada sistema de destino.
  • Los intérpretes de bytecode (estoy viendo Java aquí) recientemente tienden a volver a compilar el bytecode que obtienen para la CPU de la sección de destino justo antes de la ejecución (llamado JIT). Para ahorrar tiempo, esto a menudo solo se hace para el código que se ejecuta con frecuencia (puntos de acceso).
  • Algunos sistemas que se ven y actúan como intérpretes (Clojure, por ejemplo) compilan cualquier código que obtienen, inmediatamente, pero permiten el acceso interactivo al entorno del programa. Esa es básicamente la conveniencia de los intérpretes con la velocidad de la compilación binaria.
  • Algunos compiladores realmente no compilan, simplemente predigerieron y comprimieron el código. Escuché hace un tiempo cómo funciona Perl. Entonces, a veces el compilador solo está haciendo un poco del trabajo y la mayor parte sigue siendo interpretación.

Al final, en estos días, la interpretación frente a la compilación es una compensación, ya que el tiempo dedicado (una vez) a la compilación a menudo se ve recompensado por un mejor rendimiento en tiempo de ejecución, pero un entorno interpretativo que brinda más oportunidades para la interacción. La compilación frente a la interpretación es principalmente una cuestión de cómo el trabajo de "comprender" el programa se divide entre diferentes procesos, y la línea está un poco borrosa en estos días a medida que los idiomas y los productos intentan ofrecer lo mejor de ambos mundos.

Carl Smotricz
fuente
23

De http://www.quora.com/What-is-the-difference-between-compiled-and-interpreted-programming-languages

No hay diferencia, porque "lenguaje de programación compilado" y "lenguaje de programación interpretado" no son conceptos significativos. Cualquier lenguaje de programación, y realmente quiero decir cualquiera, puede interpretarse o compilarse. Por lo tanto, la interpretación y la compilación son técnicas de implementación, no atributos de lenguajes.

La interpretación es una técnica mediante la cual otro programa, el intérprete, realiza operaciones en nombre del programa que se está interpretando para ejecutarlo. Si puede imaginarse leyendo un programa y haciendo lo que dice hacer paso a paso, digamos en un papel de borrador, eso también es lo que hace un intérprete. Una razón común para interpretar un programa es que los intérpretes son relativamente fáciles de escribir. Otra razón es que un intérprete puede monitorear lo que un programa intenta hacer mientras se ejecuta, para hacer cumplir una política, por ejemplo, por seguridad.

La compilación es una técnica mediante la cual un programa escrito en un idioma (el "idioma fuente") se traduce a un programa en otro idioma (el "lenguaje objeto"), lo que con suerte significa lo mismo que el programa original. Al hacer la traducción, es común que el compilador también intente transformar el programa de manera que acelere el programa objeto (¡sin cambiar su significado!). Una razón común para compilar un programa es que hay una buena forma de ejecutar programas en el lenguaje de objetos rápidamente y sin la sobrecarga de interpretar el idioma de origen en el camino.

Es posible que haya adivinado, según las definiciones anteriores, que estas dos técnicas de implementación no son mutuamente excluyentes e incluso pueden ser complementarias. Tradicionalmente, el lenguaje objeto de un compilador era el código de máquina o algo similar, que se refiere a cualquier número de lenguajes de programación entendidos por CPU de computadoras particulares. El código de la máquina entonces se ejecutaría "en el metal" (aunque uno podría ver, si se mira lo suficientemente de cerca, que el "metal" funciona muy parecido a un intérprete). Hoy, sin embargo, es muy común usar un compilador para generar código de objeto que debe interpretarse; por ejemplo, así es como Java solía (y a veces todavía funciona). Hay compiladores que traducen otros idiomas a JavaScript, que a menudo se ejecuta en un navegador web, que podría interpretar el JavaScript, o compilarlo en una máquina virtual o código nativo. También tenemos intérpretes para el código de máquina, que pueden usarse para emular un tipo de hardware en otro. O bien, uno podría usar un compilador para generar código objeto que luego es el código fuente de otro compilador, que incluso podría compilar código en la memoria justo a tiempo para que se ejecute, lo que a su vez. . . entiendes la idea. Hay muchas formas de combinar estos conceptos.

Bhavin Shah
fuente
¿Puede arreglar esta frase: "Hay compiladores que traducen otros lenguajes a JavaScript, que a menudo se ejecutan en un navegador web, que pueden interpretar el JavaScript o compilarlo en una máquina virtual o código nativo".
Koray Tugay
Justo en el clavo. Otro error común es atribuir la utilidad de un lenguaje a sus API existentes.
Little Endian el
10

La mayor ventaja del código fuente interpretado sobre el código fuente compilado es la PORTABILIDAD .

Si su código fuente está compilado, necesita compilar un ejecutable diferente para cada tipo de procesador y / o plataforma en la que desea que se ejecute su programa (por ejemplo, uno para Windows x86, uno para Windows x64, uno para Linux x64, y así en). Además, a menos que su código cumpla completamente con los estándares y no use ninguna función / biblioteca específica de la plataforma, ¡realmente necesitará escribir y mantener múltiples bases de código!

¡Si su código fuente es interpretado, solo necesita escribirlo una vez y puede ser interpretado y ejecutado por un intérprete apropiado en cualquier plataforma! Es portátil ! Tenga en cuenta que un intérprete en sí es un programa ejecutable que es escrito y compilado para una plataforma específica.

Una ventaja del código compilado es que oculta el código fuente del usuario final (que podría ser propiedad intelectual ) porque en lugar de implementar el código fuente original legible por humanos, implementa un archivo ejecutable binario oscuro.

Niko Bellic
fuente
1
En estos términos, Java no puede considerarse un "lenguaje compilado", pero su fase de compilación ofrece las ventajas de la compilación (verificación de tipo, detección temprana de errores, etc.) y produce código de bytes que se puede ejecutar en cada sistema operativo, con Java Máquina virtual proporcionada.
Rogelio Triviño
7

Un compilador y un intérprete hacen el mismo trabajo: traducir un lenguaje de programación a otro lenguaje de programación, generalmente más cerca del hardware, a menudo código de máquina ejecutable directo.

Tradicionalmente, "compilado" significa que esta traducción se realiza de una vez, es realizada por un desarrollador y el ejecutable resultante se distribuye a los usuarios. Ejemplo puro: C ++. La compilación generalmente lleva bastante tiempo e intenta hacer muchas optimizaciones costosas para que el ejecutable resultante se ejecute más rápido. Los usuarios finales no tienen las herramientas y el conocimiento para compilar cosas ellos mismos, y el ejecutable a menudo debe ejecutarse en una variedad de hardware, por lo que no puede hacer muchas optimizaciones específicas de hardware. Durante el desarrollo, el paso de compilación por separado significa un ciclo de retroalimentación más largo.

Tradicionalmente, "interpretado" significa que la traducción ocurre "sobre la marcha", cuando el usuario quiere ejecutar el programa. Ejemplo puro: PHP vainilla. Un intérprete ingenuo tiene que analizar y traducir cada pieza de código cada vez que se ejecuta, lo que lo hace muy lento. No puede hacer optimizaciones complejas y costosas porque tomarían más tiempo que el tiempo ahorrado en la ejecución. Pero puede usar completamente las capacidades del hardware en el que se ejecuta. La falta de un paso de compilación por separado reduce el tiempo de retroalimentación durante el desarrollo.

Pero hoy en día "compilado vs. interpretado" no es un problema en blanco y negro, hay sombras en el medio. Ingenuos, simples intérpretes están prácticamente extintos. Muchos idiomas usan un proceso de dos pasos donde el código de alto nivel se traduce a un código de bytes independiente de la plataforma (que es mucho más rápido de interpretar). Luego tiene "compiladores justo a tiempo" que compilan el código como máximo una vez por ejecución del programa, a veces los resultados de la memoria caché, e incluso deciden de manera inteligente interpretar el código que se ejecuta raramente, y realizan optimizaciones potentes para el código que se ejecuta mucho. Durante el desarrollo, los depuradores pueden cambiar el código dentro de un programa en ejecución incluso para los lenguajes compilados tradicionalmente.

Michael Borgwardt
fuente
1
Sin embargo, el modelo de compilación de C ++ se hereda de C y se diseñó sin tener en cuenta características como las plantillas. Esta incomodidad contribuye a los largos tiempos de compilación de C ++ mucho más que cualquier otro factor, y lo convierte en un mal ejemplo.
4

Primero, una aclaración, Java no está completamente compilado estático y vinculado en la forma C ++. Se compila en bytecode, que luego es interpretado por una JVM. La JVM puede ir y hacer una compilación justo a tiempo para el lenguaje nativo de la máquina, pero no tiene que hacerlo.

Más concretamente: creo que la interactividad es la principal diferencia práctica. Como todo está interpretado, puede tomar un pequeño extracto de código, analizarlo y ejecutarlo contra el estado actual del entorno. Por lo tanto, si ya hubiera ejecutado código que inicializara una variable, tendría acceso a esa variable, etc. Realmente se presta a cosas como el estilo funcional.

Sin embargo, la interpretación cuesta mucho, especialmente cuando tienes un sistema grande con muchas referencias y contexto. Por definición, es un desperdicio porque es posible que deba interpretarse y optimizarse un código idéntico dos veces (aunque la mayoría de los tiempos de ejecución tienen cierto almacenamiento en caché y optimizaciones para eso). Aún así, paga un costo de tiempo de ejecución y, a menudo, necesita un entorno de tiempo de ejecución. También es menos probable que vea optimizaciones complejas entre procedimientos porque en la actualidad su rendimiento no es lo suficientemente interactivo.

Por lo tanto, para sistemas grandes que no van a cambiar mucho, y para ciertos idiomas, tiene más sentido precompilar y vincular todo, hacer todas las optimizaciones que pueda hacer. Esto termina con un tiempo de ejecución muy reducido que ya está optimizado para la máquina de destino.

En cuanto a la generación de ejecutables, eso tiene poco que ver con eso, en mi humilde opinión. A menudo puede crear un ejecutable desde un lenguaje compilado. Pero también puede crear un ejecutable a partir de un lenguaje interpretado, excepto que el intérprete y el tiempo de ejecución ya están empaquetados en el ejecutable y ocultos para usted. Esto significa que generalmente todavía paga los costos de tiempo de ejecución (aunque estoy seguro de que para algunos idiomas hay formas de traducir todo a un árbol ejecutable).

No estoy de acuerdo con que todos los idiomas puedan ser interactivos. Ciertos lenguajes, como C, están tan vinculados a la máquina y a toda la estructura de enlaces que no estoy seguro de que pueda crear una versión interactiva con todas las de la ley.

Uri
fuente
C no está realmente atado a una "máquina". La sintaxis y la semántica de C son bastante simples. Implementar un intérprete en C no debería ser particularmente difícil, solo requiere mucho tiempo (porque la biblioteca estándar también debe implementarse). Y por cierto, Java se puede compilar en código máquina nativo (usando gcj).
lunaryorn
@lunaryorn: No estoy de acuerdo con GCJ. GCJ simplemente le ofrece un entorno basado en ejecutable. "Las aplicaciones compiladas están vinculadas con el tiempo de ejecución de GCJ, libgcj, que proporciona las bibliotecas de clases principales, un recolector de basura y un intérprete de código de bytes"
Uri
2
GCJ hace código máquina nativo productos, y no sólo un entorno de ejecución con el intérprete incorporado y código de bytes. libgcj proporciona un intérprete de código de bytes para admitir llamadas del código nativo al código de bytes de Java, no para interpretar el programa compilado. Si libgcj no proporcionó un intérprete de código de bytes, GCJ no cumpliría con la especificación de Java.
lunaryorn
@lunaryorn: Ah. Ok, agradezco la aclaración y me corrijo. Principalmente usamos Java en un entorno de Windows, así que no he probado gcj en años.
Uri
2

Es bastante difícil dar una respuesta práctica porque la diferencia está en la definición del lenguaje en sí. Es posible construir un intérprete para cada lenguaje compilado, pero no es posible construir un compilador para cada lenguaje interpretado. Se trata mucho de la definición formal de un idioma. Así que la informática teórica no le gusta a los noboby en la universidad

Steven Mohr
fuente
1
Seguramente puede construir un compilador para un lenguaje interpretado, pero el código de máquina compilado es en sí mismo un espejo del tiempo de ejecución.
Aiden Bell
2

The Python Book © 2015 Imagine Publishing Ltd, simplemente distingue la diferencia por la siguiente pista mencionada en la página 10 como:

Un lenguaje interpretado como Python es aquel en el que el código fuente se convierte en código de máquina y luego se ejecuta cada vez que se ejecuta el programa. Esto es diferente de un lenguaje compilado como C, donde el código fuente solo se convierte en código de máquina una vez; el código de máquina resultante se ejecuta cada vez que se ejecuta el programa.

Ahmed Shaban Helwa
fuente
1

Compilar es el proceso de crear un programa ejecutable a partir de código escrito en un lenguaje de programación compilado. La compilación permite que la computadora ejecute y comprenda el programa sin la necesidad del software de programación utilizado para crearlo. Cuando se compila un programa, a menudo se compila para una plataforma específica (por ejemplo, la plataforma de IBM) que funciona con computadoras compatibles con IBM, pero no con otras plataformas (por ejemplo, la plataforma de Apple). El primer compilador fue desarrollado por Grace Hopper mientras trabajaba en la computadora Harvard Mark I. Hoy en día, la mayoría de los lenguajes de alto nivel incluirán su propio compilador o tendrán kits de herramientas disponibles que pueden usarse para compilar el programa. Un buen ejemplo de un compilador utilizado con Java es Eclipse y un ejemplo de un compilador utilizado con C y C ++ es el comando gcc.

salehvm
fuente
0

Definición breve (no precisa):

Lenguaje compilado: todo el programa se traduce al código de la máquina a la vez, luego el código de la máquina lo ejecuta la CPU.

Lenguaje interpretado: programa se lee línea por línea y tan pronto como se lee una línea, la CPU ejecuta las instrucciones de la máquina para esa línea.

Pero realmente, pocos idiomas en estos días se compilan o interpretan puramente, a menudo es una mezcla. Para una descripción más detallada con imágenes, vea este hilo:

¿Cuál es la diferencia entre compilación e interpretación?

O mi posterior publicación de blog:

https://orangejuiceliberationfront.com/the-difference-between-compiler-and-interpreter/

uli testigo
fuente