lenguajes-compilados-e-interpretados

Lenguajes compilados, interpretados y semi-interpretados

  • 5 min

Cuando programamos, escribimos código en lenguajes como C++, Python o C#. Es útil para las personas 🙋‍♀️, porque lo entendemos (o, al menos, eso intentamos).

Pero tu ordenador no tiene ni idea de lo que estás diciendo. Como hemos visto, el procesador solo entiende de ceros y unos. Es decir, código máquina.

Por tanto, necesitamos “algo” que traduzca nuestro texto legible por humanos (High Level Language) a instrucciones que la máquina pueda ejecutar.

Dependiendo de cómo y cuándo se haga esta traducción, clasificamos los lenguajes en Compilados, Interpretados o Semi-interpretados.

Diferencias y comparaciones

En función de que un lenguaje sea de cada tipo, ya tiene una serie de características propias que van a definir parte de su comportamiento.

Veamos una tabla de resumen de alguna de ellas,

{ “models”: [ { “name”: “Compilados” }, { “name”: “Semicompilados (JIT)”}, { “name”: “Interpretados” } ], “specs”: { “Velocidad de ejecución”: [ { “grade”: “very-high”, “display”: “Muy rápida”, “sub”: “Nativo” }, { “grade”: “high”, “display”: “Rápida”, “sub”: “Caercana a compilado” }, { “grade”: “low”, “display”: “Limitada”, “sub”: “Interpretación por línea” } ], “Portabilidad”: [ { “grade”: “low”, “display”: “Baja”, “sub”: “Requiere recompilar” }, { “grade”: “high”, “display”: “Alta”, “sub”: “Bytecode” }, { “grade”: “very-high”, “display”: “Universal”, “sub”: “Código fuente portable” } ], “Fases de desarrollo”: [ “Compilación previa (AOT)”, “Compilación JIT en ejecución”, “Interpretación en tiempo real” ], “Ejemplos”: [ “C, C++, Rust”, “Java, C#”, “Python, JavaScript” ] } }

Esta clasificación no es estanca. Hoy en día, las fronteras son difusas y muchos lenguajes modernos utilizan técnicas híbridas para obtener lo mejor de ambos mundos.

Ahora vamos a intentar entender porque tiene características distintas, mirando `bajo el capó” de cada uno de ellos 👇.

Lenguajes compilados

En los lenguajes compilados, como C, C++, Rust, Go, la traducción se realiza completamente antes de ejecutar el programa.

En estos lenguajes, nosotros escribimos el código fuente y después utilizamos un programa llamado Compilador. Este analiza todo nuestro código, busca errores y, si todo está bien, genera un archivo binario (el famoso .exe en Windows, por ejemplo).

Este archivo resultante contiene instrucciones en código máquina puro, listas para ser ejecutadas por el procesador.

  • Rendimiento: Al estar ya traducido, el programa va a toda velocidad. El procesador no pierde tiempo traduciendo mientras ejecuta.
  • Optimización: El compilador puede analizar el código entero y realizar optimizaciones complejas antes de generar el binario.
  • Menos flexibles: Si quieres cambiar una línea de código, tienes que volver a compilar todo.
  • Dependencia de plataforma: Un ejecutable compilado para Windows no funciona en Linux, y uno compilado para un procesador Intel no funciona en un ARM (como un móvil). Necesitas compilar una versión para cada arquitectura.

Lenguajes interpretados

En el otro extremo tenemos los lenguajes interpretados, como Python, PHP, JavaScript (originalmente). Aquí no hay una traducción previa.

En su lugar, tenemos un programa llamado Intérprete. El intérprete va leyendo nuestro código fuente línea por línea, lo traduce a código máquina en ese instante y lo ejecuta.

  • Portabilidad: Puedes llevar tu código fuente a cualquier máquina. Si tiene el intérprete instalado, funcionará.
  • Desarrollo rápido: Haces un cambio, guardas y ejecutas. No hay tiempo de espera de compilación.
  • Menor rendimiento: El ordenador tiene que trabajar el doble en tiempo de ejecución (traducir + ejecutar), por lo que suelen ser más lentos que los compilados.

Python técnicamente compila a bytecode (.pyc) de forma transparente, pero a efectos prácticos se comporta como interpretado.

JavaScript usa motores JIT muy potentes (como V8 en Chrome), por lo que actualmente es semi-interpretado.

Lenguajes Semi-interpretados (Híbridos)

Aquí es donde la frontera se vuelve difusa. Lenguajes como Java o C# buscaron un punto medio para tener portabilidad y buen rendimiento.

El proceso tiene dos pasos:

Compilación a Bytecode: Nuestro código fuente se compila, pero no a código máquina, sino a un lenguaje intermedio (Bytecode en Java, CIL en .NET).

La máquina virtual: Ese código intermedio se ejecuta sobre una “Máquina Virtual” (JVM o CLR) instalada en el ordenador del usuario.

Este código intermedio es universal. El mismo archivo compilado de Java funciona en Windows, Linux o Mac, siempre que tengan la Máquina Virtual instalada.

  • Equilibrio Potencia/Flexibilidad: Combinan una velocidad de ejecución muy alta (cercana a nativo gracias al JIT) con la enorme ventaja de la portabilidad del Bytecode.
  • Optimizaciones dinámicas: Al compilar en tiempo real, el JIT puede realizar optimizaciones inteligentes basadas en cómo se está usando el programa en ese instante concreto, algo que un compilador estático (AOT) no puede prever.
  • Tiempo de arranque (Warm-up): El programa suele tardar un poco más en iniciar que uno compilado nativo, ya que debe cargar la Máquina Virtual y empezar a “calentar” (compilar) las primeras instrucciones.
  • Mayor consumo de memoria: Tienen una huella de memoria más grande (overhead), ya que el ordenador debe cargar el programa, la Máquina Virtual y el propio compilador JIT simultáneamente.