Tal vez después de Profile Guided Optimizations (PGO), si hubo una característica que me impresionó de los compiladores, fue el Function Level Linking, debería decir en realidad de los enlazadores, ya que su significado es Enlazado a Nivel de Función.
Veréis, cuando comencé con los lenguajes de programación compilados, usando Turbo BASIC, me di cuenta que de manera sistemática, un ejecutable para DOS, aunque estuviera vacío, ocupaba mínimo 30 Kb. en disco (exactamente 34.704 bytes con TB 1.1). A partir de ahí, el código que iba escribiendo, apenas aumentaba el tamaño del EXE resultante. Descubrí que podía ir jugando con algunas opciones del compilador, y entonces ese código fijo, se reducía a 27.184 bytes. En aquel momento no lo sabía con detalle, pero me imaginaba que serían los runtimes de Turbo BASIC necesarios para ejecutar el programa. Realmente no iba muy desencaminado, esos 30 Kb., era el código de inicialización como yo pensaba, pero además la biblioteca de funciones que usarían mis programas (PRINT, INPUT, OPEN, …).
Después con Turbo C, se me abrió el camino del enlazador. Con Turbo C, había un compilador (TCC) que se encargaba de transformar archivos .C (código fuente), a archivos .OBJ (código objeto), y luego un enlazador (TLINK), que unía esos archivos de código objeto en un .EXE (ejecutable). La ventaja era que si no usábamos el código de algún archivo, éste, no acababa mentiéndose en el ejecutable final. Era sin duda algo muy interesante, y que permitía que el tamaño de los programas, creciera de manera lineal, siendo muy pequeños los que eran simples, y más grandes, los que eran más complicados. Incluso aparecieron los archivos de librería o biblioteca .LIB, que básicamente eran una agrupación de archivos .OBJ, que se empaquetaban en un archivo único. Así que podíamos usar librerías tan grandes como Turbo Vision, y nuestros programas no las contendrían al completo. Tendrían sólo las partes que utilizábamos.
Desgraciadamente, no era así, ya que esos archivos .OBJ, provenían de archivos .C (o .CPP o .PAS), que tenían varias funciones relacionadas dentro, o la implementación de un objeto completo. Entonces ocurría, que cuando usábamos printf, se incluía también el código de sprintf, o cosas parecidas, aunque no las usáramos realmente. Era una buena mejora, pero parecía de cajón que el enlazador debiera ser más “listo”.
Mientras tanto, Turbo BASIC había evolucionado, y con Power BASIC 3.5, los runtimes estaban divididos en bloques, de manera que podíamos incluirlos o no, de una forma más granular. Un programa al completo, sería como mínimo 44.828 bytes, mucho más que el Turbo BASIC original, debido a que incluía muchísimas más posibilidades, pero que podíamos reducir hasta 27.548 bytes, si desactivábamos cosas que no necesitásemos. Lo malo, es que teníamos que seleccionarlo a priori, y a mano. De nuevo, debería ser más “listo”.
Finalmente, aparecería el function level linking, creo que lo vi por primera vez en Symantec C++, o en Watcom C++. Sus enlazadores, intentaban incluir en el ejecutable, solamente las funciones que usábamos, independientemente de en que archivo objeto estuvieran, o si convivieran con muchas otras. Por fin lo teníamos. Sin embargo, tardó mucho en llegar, primero a Microsoft C++ y muchísimo más tarde a Borland C++, y ese era el rasgo, que para mi diferenciaba un buen compilador, del resto.
En enlazado a nivel de función, tardó todavía más en llegar a otros lenguajes de programación populares, especialmente, a aquellos que no utilizaban un enlazador propiamente dicho. A Lazarus / FreePascal no llegó hasta 2010, y a PowerBASIC hasta 2011. A Delphi aún no lo ha hecho, y en Clipper no ocurrió nunca.
A ésta técnica, se la conoce también en tiempos más recientes como Whole Program Optimization (WTO), debido a que el compilador, debe ser capaz de marcar las funciones que se utilizan y las que no lo hacen.
No tenía ni idea que Lazarus tuviese enlazado a nivel de función. Nunca me dio por detenerme en esas cosas. aparte de por la popularidad del lenguaje, supongo que en C++ se le puede sacar más rendimiento a ese tipo de características que en otros lenguajes, y por eso es comprensible que las recibiera el primero.
En realidad más que por las características del lenguaje, ser trata de su implementación. C, siempre ha usado un compilador/compiler (de fuente a objeto), y luego un enlazador/linker, que juntaba esos archivos objeto, y los unía en un ejecutable. El enlazador, lo tenía más fácil para analizar esos archivos objeto, y ver “lo que sobreba”.
En cambio, típicamente Turbo Pascal Pascal, Turbo Basic o incluso Delphi, no tenían tal enlazador. Eso les permitía ser mucho más rápidos compilando, ya que el compilador, generaba directamente los ejecutables, sin pasar por un enlazador. Incluso eran capaces de compilar a memoria, de manera que no se escribiera ejecutable alguno. Simplemente compilaban, y lanzaban tu programa directamente desde memoria. Por eso en sus días, Delphi, y Borland Pascal, batieron records en cuanto a cantidad de lineas compiladas por segundo. Eran rapídísimos.
gracias por el artículo
Gracias por leerlo Manuel.