Menú de navegaciónMenú
Categorías

La mejor forma de Aprender Programación online y en español www.campusmvp.es

Conceptos esenciales sobre compilación e interpretación

Si programas habitualmente en unos pocos lenguajes, tendrás la costumbre de seguir siempre los mismos pasos para ejecutar los programas que escribas. Según el lenguaje, eso involucrará utilizar un intérprete, transformar tu código a ejecutable mediante un compilador, o incluso utilizar herramientas de automatización para pasar de un punto a otro. En este artículo nos interesa estudiar cuál es realmente la tarea que cumplen todos estos sistemas y qué los diferencia.

Antecedentes

Primero, no debemos considerar la compilación y la interpretación como la única forma de producir y tratar programas.

Por un lado, existen lenguajes y programas teóricos, es decir, que no llegan a ejecutarse en un ordenador pero también tienen su importancia, al poder razonar sobre ellos de forma lógica: el cálculo lambda de Church es un claro ejemplo. Se trata de un lenguaje diseñado con el razonamiento lógico como propósito, que incorpora reglas sencillas de cálculo (sustituciones y reducciones) para resolver las expresiones. Así, para este lenguaje existen tanto intérpretes como teoremas matemáticos.

Por otro lado, otro tipo de programas que no se interpretan ni compilan son los que se componen directamente en el lenguaje de la máquina que los va a ejecutar. En particular, los primeros programas de ordenador, escritos por Ada Lovelace entre 1842 y 1843, consistían en unas notas especificando en tablas y diagramas cómo realizar el cálculo de números de Bernoulli en la máquina analítica de Charles Babbage. Esta máquina, que podía realizar procesamiento de propósito general, era meramente un diseño y no se pudo construir hasta un siglo después (pese a ello, se pudo comprobar que los programas de Lovelace eran correctos). El equivalente actual sería escribir un programa en ensamblador, que se puede convertir directamente a código máquina transformando cada instrucción en su equivalente en binario.

Por tanto, la compilación y la interpretación son necesarias cuando la intención es ejecutar nuestro programa en una máquina, y el lenguaje de nuestro programa es una abstracción, es decir, no se corresponde directamente con el lenguaje máquina. En este sentido, podemos verlas como tareas de traducción, de un lenguaje origen de alto nivel (el de programación) a un lenguaje objetivo de bajo nivel.

El primer lenguaje de programación de alto nivel fue Plankalkül, diseñado entre 1942 y 1945, pero el primero con una implementación real y de uso común fue Fortran en 1954.

Compilación

La compilación es la conversión de código en un lenguaje en otro, en un paso previo a su ejecución. Normalmente cuando pensamos en compilación hablamos de su versión más tangible, aquella que nos da un binario ejecutable como salida. Se suele denominar a este tipo de compilación ahead-of-time (AOT). Muchos de los lenguajes clásicos y más rápidos se utilizan con compiladores AOT: desde Fortran hasta Rust, pasando por C y C++. Este tipo de compilación permite realizar optimizaciones complejas, por muy costosas que sean, y adaptar el ejecutable final a la máquina donde se va a ejecutar (el proceso de compilar para una plataforma distinta de la de compilación se llama compilación cruzada).

Si la compilación no es de este tipo, puede ser porque no veamos el binario generado, sino que directamente se pase a su ejecución, estrategia conocida como compilación just-in-time o JIT. Esta permite aplicar ciertas optimizaciones que aprovechen la información disponible en tiempo de ejecución sobre la máquina, pero no puede realizar todas las optimizaciones AOT, ya que esto generaría un retraso muy perceptible en la ejecución. Para optimizar al máximo el rendimiento al usar compilación JIT, algunos lenguajes realizan un paso de pre-compilación intermedia que no genera código máquina sino bytecode. En ese paso se trata de conseguir producir un código resultante de bajo nivel independiente de la plataforma de ejecución, de forma que la compilación JIT sea más rápida. Conocidos lenguajes que suelen utilizar compilación a bytecode y JIT son Java y C#. La mayoría de implementaciones de JavaScript son también compiladores JIT.

Por otro lado, cuando la compilación no genera un binario en código máquina sino un resultado en otro lenguaje de programación, se trata de una compilación fuente-a-fuente (source-to-source), en ocasiones conocida como transpilación. Los casos más comunes de transpilación los encontramos en tecnologías web, ya que los navegadores principalmente permiten ejecutar código JavaScript (aunque WebAssembly está llegando), por lo que si queremos programar en el front-end con otro lenguaje debemos compilar a JS. El interés de esto reside en que el lenguaje origen puede proveer características que no estén disponibles en el objetivo, como tipado estático, programación funcional, otras orientaciones a objetos, etc. Algunos lenguajes que se transpilan son TypeScript, PureScript o Dart.

Interpretación y lenguajes interpretados

La forma alternativa de ejecutar un programa a partir del código en un lenguaje de programación es no generar una traducción a código máquina, sino analizar el código y realizar los cómputos que éste indique, bien directamente o bien a partir algún tipo de representación intermedia que no constituya un programa en código máquina. En este caso se dice que el programa es interpretado. Mientras se produce la interpretación, debe ejecutarse en el sistema el programa que la realiza, es decir, el intérprete del lenguaje. Este puede estar interpretado también, en cuyo caso hace falta un programa al final de la cadena de intérpretes que sí esté en código máquina.

Las representaciones intermedias que puede generar el intérprete son generalmente de dos tipos: un código de bajo nivel o una estructura de datos. El código de bajo nivel puede ser bytecode como en compilación JIT, o código enhebrado que consiste únicamente en llamadas a subrutinas del intérprete. Las estructuras de datos, por otro lado, suelen ser árboles de sintaxis abstracta (AST) que se van recorriendo para obtener los resultados de la ejecución. Estas últimas no son tan utilizadas ya que producen mayores sobrecostes que los códigos de bajo nivel. Ejemplos de lenguajes típicamente interpretados son Python (con bytecode), Ruby (usaba ASTs hasta la versión 1.8), PHP (con bytecode) y Perl (utiliza ASTs).

La interpretación de bytecode y la compilación JIT son muy similares, y las diversas implementaciones que existen forman más un espectro que dos categorías diferenciadas. Podríamos considerar que un bytecode es interpretado cuando su traducción a código máquina y su ejecución están totalmente entrelazadas, mientras que es compilado cuando primero se genera la traducción y posteriormente se ejecuta. Sin embargo, muchos intérpretes JIT (conocidos como máquinas virtuales) traducen el bytecode a trozos, en los momentos en que se prevé que son necesarios, desdibujando así la línea entre ambas estrategias.

Fuentes y más información:

David Charte David Charte es ingeniero informático y matemático, con un doctorado en Ciencia de Datos. Es un apasionado del conocimiento y la divulgación. Tiene amplia experiencia en el desarrollo de aplicaciones utilizando diversos lenguajes y plataformas. En la actualidad trabaja en Idoven, una empresa que usa Inteligencia Artificial para detectar enfermedades cardiovasculares. Ver todos los posts de David Charte
Archivado en: General

Boletín campusMVP.es

Solo cosas útiles. Una vez al mes.

🚀 Únete a miles de desarrolladores

DATE DE ALTA

x No me interesa | x Ya soy suscriptor

La mejor formación online para desarrolladores como tú

Agregar comentario

Los datos anteriores se utilizarán exclusivamente para permitirte hacer el comentario y, si lo seleccionas, notificarte de nuevos comentarios en este artículo, pero no se procesarán ni se utilizarán para ningún otro propósito. Lee nuestra política de privacidad.