miércoles, 19 de septiembre de 2012

Compilación y bibliotecas (parte 2): Intérpretes vs Compiladores

A la hora de ejecutar un programa escrito con algún lenguaje de programación hay, en principio, dos grandes opciones: interpretar y compilar. Digo en principio, porque se podría discutir un buen rato sobre dónde ubicar lenguajes administrados, máquinas virtuales por ejemplo, o intérpretes que utilizan algún formato intermedio que no es ni fuente ni objeto, pero simplificando en los dos casos de libro más claros, tenemos intérpretes y compiladores. Un compilador es un programa que "traduce" el código fuente de un lenguaje de alto nivel a código de máquina listo para ser ejecutado. Un intérprete, en cambio, es otro programa que interpreta las instrucciones del código de alto nivel e intenta ejecutarlas él mismo. La diferencia en principio no parece clara, pero pongamos una metáfora. Si quiero escuchar una canción, digamos "Over the rainbow", la hoja con la partitura y la letra serían el código fuente. Un compilador sería un músico que toma la partitura, y graba la canción por ejemplo en un cd de audio que voy a poder escuchar en mi auto cuando quiera aunque el músico se haya retirado, vendido su guitarra y quemado la partitura. Un intérprete en cambio sería un músico que toca la canción en vivo para mi, leyendo de la partitura cada vez que quiero escucharla, y que tengo que llevar sentado con su guitarra en el asiento del acompañante todo el tiempo. Parece poco cómodo, pero tiene sus ventajas. Digamos que quiero probar cómo quedaría si agrego algunos arreglos, o si cambio la letra por otra nueva que se me ocurrió, o si afino la guitarra un semitono más arriba. En el primer caso, tendría que llamar al músico "compilador" para volver a grabar un nuevo cd, mientras que en el segundo tendría que hacer lo mismo de siempre, pedirle al músico "intérprete" que interprete una vez más la partitura, que esta vez estará modificada.


Volviendo a los programas, si quiero un programa que calcule un promedio, por ejemplo, puedo escribirlo en C++ y compilarlo. El resultado de la compilación es un archivo ejecutable listo para usar. Una vez compilado, no necesito más ni al compilador ni al código fuente. En cambio, si lo escribo en perl, cada vez que quiera ejecutarlo tengo que pedirle al intérprete de perl que interprete y ejecute esas instrucciones. Como ventaja de la compilación, un programa compilado será en general mucho más rápido y requerirá menos memoria si el compilador hace bien su trabajo. Otra desventaja ya mencionada de un programa interpretado es que para ejecutarlo necesito tener siempre tanto el intérprete como el código fuente. Sin embargo, en un lenguaje interpretado el desarrollo suele ser más fácil, ya que el código fuente se puede modificar más fácilmente y los cambios se pueden observar directamente, sin tener que recompilar, y muchas veces sin siquiera tener reiniciar el programa. Además, dado que el intérprete puede seguir instrucciones directamente, durante la depuración utilizamos esa misma capacidad para inspeccionar cosas y alterar el flujo de la ejecución por ejemplo. Por otro lado, el compilador debe resolver todos y cada uno de los problemas que encuentre para traducir al momento de compilar, lo cual es antes de ejecutar, y por ende sin saber qué datos va a recibir el programa y cual va a ser el flujo de la ejecución. El intérprete, en cambio, va interpretando instrucción a instrucción a medida que necesita, y entonces puede darse el lujo de, por ejemplo, no definir el tipo de una variable hasta no saber qué datos le ingresa el usuario, de forma que este tipo pueda cambiar entre dos ejecuciones. Por eso, los lenguajes interpretados dan muchas más facilidades, son más flexibles, pero cuando la eficiencia es lo que importa, los lenguajes compilados son sin duda los ganadores.

Usualmente la condición de interpretado o compilado está asociada al lenguaje, aunque es más correcto decir que uso un compilador de C++ antes que decir que C++ es un lenguaje compilado. Teoricamente podría existir un intérprete para C++ (de hecho recuerdo haber leido sobre algún intento para el caso de C), pero es poco probable porque el lenguaje está pensado desde el vamos para ser compilado. Por la naturaleza de sus reglas, es mucho más lógico seguirlas con un compilador que con un intérprete. De igual forma, otros lenguajes están concebidos para ser interpretados y dan libertades que le complicarían irremediablemente la vida a cualquier intento de compilador. PSeInt, por ejemplo, es un intérprete, y por eso no se genera un exe, sino que para ejecutar un algoritmo hay que instalarlo sí o sí. C++, como ya dije antes, es un lenguaje compilado, por eso pueden jugar al MotoGT sin tener que instalar gcc. Basic puede ser un ejemplo de un lenguaje para el cual hay una muy amplia gama tanto de intérpretes como de compiladores. Python es un lenguaje interpretado, pero el intérprete tiene algunas características más propias de un compilador. Y así hay muchos ejemplos de lenguajes que encajan perfectamente en una categoría, y también muchos ejemplos más bien grises.

Finalmente, vale aclarar que en un proyecto complejo suelen convivir partes escritas en lenguajes compilados y partes interpretadas. La flexibilidad de un intérprete se suele aprovechar por ejemplo para los mecanismos de plugins. Es común que un proyecto tenga sus parte más importantes y críticas en un lenguaje compilado, y las extensiones, configuración, plugins, y otros agregados en un lenguaje interpretado. En videojuegos, por ejemplo, la parte de física, gráficos y sonido suele ser compilada por razones de eficiencia, mientras que la definición de escenarios, propiedades de personajes, eventos de un nivel, etc puede tomarse desde un sistema de tipo scripting utilizando un interprete propio del juego, o integrando algún intérprete genérico mediante una biblioteca (por ejemplo lua). De esta forma, el motor es eficiente en las partes críticas (rendering y física por ejemplo), pero el diseño del juego es más llevadero (cambiar algunas reglas, configurar escenarios, modificar propiedades de los personajes, alterar la dificultad de un nivel, son cosas que se pueden hacer muy fácilmente, aún sin ser programador, si están definidas en un script).

Este post es continuación de Compilación y bibliotecas (parte 1): ¿por qué? y sigue en Compilación y bibliotecas (parte 3): ese oscuro y mágico proceso 

1 comentario: