Copyright Copyright © José Luis Lara Carrascal 2012-2024 Sumario Introducción Instalación Configurar el sistema para el uso de Clang Optimizaciones de CPU para Clang Niveles de optimización soportados por Clang Optimizaciones adicionales para Clang LLD - El enlazador dinámico de LLVM Libc++ - La librería estándar de C++ de LLVM Comparativa de resultados de optimización entre Clang y GCC Enlaces LLD - El enlazador dinámico de LLVM Inicialmente creado para versiones de 64 bits de la arquitectura X86 en sus respectivos formatos: ELF (Unix) y COFF (Windows), el enlazador dinámico alternativo a GNU ld fue reescrito desde cero en mayo de 2015, añadiendo soporte también, de ahí que lo incluyera en su día en este manual, para sistemas de 32 bits. A continuación explico cómo hacer uso del mismo con los sistemas de compilación más utilizados en nuestro sistema. Desde la versión 9 de GCC, también es compatible con este compilador. 1) Establecer el uso de enlazador dinámico para LLD 1a) GNU Autotools, CMake y sistemas de compilación que acepten la variable de entorno LDFLAGS Establecemos la correspondiente variable de entorno antes de ejecutar el script de configuración del paquete.
En aquellos procesos de compilación donde intervenga Libtool, tendremos que modificar el script incluido en el paquete de código fuente, ltmain.sh, a partir del cual se crea el script libtool, con el siguiente comando, para que Libtool soporte el parámetro fuse-ld=, antes de ejecutar el script de configuración pertinente. Como el script puede estar en el directorio raíz del paquete o en un subdirectorio, con el siguiente comando, solucionamos este inconveniente con la ejecución del mismo en el directorio raíz del paquete a configurar.
Que automatizamos y simplificamos creando una función de Bash que nos facilitará su ejecución en un proceso de compilación, incluyendo confirmación del proceso realizado. Abrimos con un editor de texto, el archivo de configuración personal, ~/.bashrc, si no existe lo creamos, y añadimos lo siguiente al final del contenido del mismo:
Cuando ejecutemos el comando lld-libtool dentro del directorio raíz del paquete a compilar que utilice Libtool para el proceso de compilación, éste modificará el archivo correspondiente para que Libtool soporte el parámetro fuse-ld=, mostrando información de la acción realizada. La versión 2.4.7 de Libtool ya soporta este parámetro, pero esto sólo es válido para paquetes que no contienen el script de configuración y hay que generarlo, ya sea con el script autogen.sh o con el comando autoreconf -vif. Los paquetes de código fuente nunca suelen tener la versión de Libtool actualizada a la última versión, de ahí que, la modificación antes explicada siga siendo necesaria. En aquellos paquetes en los que hay que generar el script de configuración, en versiones inferiores a la 2.4.7 de Libtool, lo que haremos será, modificar el script del sistema, ltmain.sh, ubicado en /usr/share/libtool/build-aux, para no tener que volver a hacerlo después de haber generado el correspondiente script de configuración.
2) Optimizar el uso de LLD cuando se hace uso de la optimización LTO o ThinLTO en los procesos de compilación con Clang Con los siguientes parámetros, aplicamos las correspondiente optimizaciones complementarias de la optimización LTO utilizada con Clang. Para no sobrecargar los procesos de compilación, recomiendo personalmente, y así, será actualizado en la información ubicada en los manuales de la web, reducir el número de procesos de enlazado a la mitad de los hilos que posea nuestro procesador.
Si nuestro procesador es mononúcleo de un solo hilo, no es necesario añadir esto. Con ThinLTO, la variable de entorno a utilizar es la siguiente:
Si nuestro procesador es mononúcleo de un solo hilo, no es necesario añadir esto. 3) Utilizar un directorio de caché de archivos objeto en los procesos de compilación con la optimización ThinLTO (desde LLVM 4 con GNU gold y desde LLVM 5 con LLD) En determinados procesos de compilación con una gran cantidad de archivos objeto a procesar por el enlazador dinámico, cuando utilizamos la optimización ThinLTO, es posible el uso de un directorio de caché específico de archivos objeto, en lugar, de utilizar el predefinido del sistema, que es /tmp. Si tenemos montado este directorio con el sistema de archivos tmpfs, la limitación de tamaño del mismo queda siempre establecida en el 50 % de la memoria física disponible en nuestro sistema. La finalidad última de utilizar un directorio caché de archivos objeto, es siempre la de acelerar el proceso de enlazado de los archivos objeto para generar el archivo binario correspondiente. En procesos de compilación que interviene Libtool, nos ahorra el tener que volver a generar dichos archivos cuando se ejecuta el proceso de instalación de los binarios compilados, ya que éstos ya estarán almacenados en el directorio de caché correspondiente. El principal inconveniente cuando estamos hablando de caché, es siempre el espacio en disco que ocupa ésta que, a medida que se vaya haciendo grande, hará el acceso a la misma más lento. Afortunadamente, con los parámetros correspondientes a pasarle a LLD, podremos controlar el tamaño de dicho directorio, evitando crear un problema en haras de intentar optimizar más todavía los procesos de compilación. Antes que nada, lo que haremos será crear el directorio de caché, procurando siempre utilizar una partición o disco duro que no sea el principal del sistema, y en la que tengamos instalada otra distribución de linux. Si no hay más remedio que utilizar la partición principal, elegiremos el directorio habitual para este tipo de archivos, /var/cache, en el que crearemos un subdirectorio con el nombre thinlto, y le daremos permisos de escritura para todos los usuarios.
Establecemos la correspondiente variable de entorno LDFLAGS, después de las explicadas anteriormente, antes de ejecutar el script de configuración del paquete:
Establecemos otra variable de entorno para controlar el tamaño del directorio de caché. En el ejemplo, un 20 % del tamaño libre disponible de la partición en la que se encuentre dicho directorio:
Las opciones de control de la caché admiten más parámetros, que se pueden añadir al anterior en color rojo, separados por dos puntos. A continuación explico todas las opciones de configuración posibles.
Una vez sabemos todas las opciones posibles, elegimos las más interesantes, y configuramos la correspondiente variable de entorno, todo en uno, relacionado con este tema concreto. Tener en cuenta que si establecemos diferentes políticas de expiración, unas terminarán solapando a las otras. Es evidente que la candidata a ser más utilizada es la del tamaño máximo disponible de espacio libre en la partición.
Probablemente algunos usuarios, entre los que me encuentro yo, prefieran utilizar un tamaño máximo de directorio, en lugar, de tener que especular con el espacio libre disponible en la partición. Un ejemplo sería éste, que establecería el límite del tamaño máximo de la caché en 2 GB.
Si utilizamos la optimización ThinLTO con GNU gold, también podemos hacer uso de dicho directorio, para almacenar los archivos objeto, pero no podremos controlar el tamaño de la caché ni el límite de caducidad en el tiempo de los archivos objeto contenidos en la misma.
4) Incluir pases de análisis y transformación de LLVM en el proceso de optimización LTO de LLD Para elevar aún más el nivel de optimización LTO de LLD, también podemos incluir en dicho proceso, los pases de análisis y transformación en tubería utilizados por LLVM (dando por hecho que hemos activado también New Pass Manager con Clang), con los siguientes parámetros, en los que incluyo un ejemplo de pase de análisis y otro de transformación, el uno seguido del otro porque deben de ir siempre en combinación.
Todos los valores posibles de pases de análisis y transformación los podemos encontrar en este archivo entrecomillados en color rojo. Unos son los de análisis (*_ANALYSIS) y otros son los de transformación (*_PASS). Uno que funciona muy bien en combinación con PGO es el siguiente:
Para saber todas las opciones posibles de LLD, ejecutamos el siguiente comando:
Y como no podía ser de otra forma, los correspondientes alias y funciones de bash para facilitar su uso, el número de núcleos se obtiene con el comando correspondiente, evitando tener que editar los alias cuando cambiemos de CPU:
Con clang-lld, activamos el uso de LLD como enlazador dinámico, con lld-lto, activamos las correspondientes optimizaciones complementarias de LTO y con lld--thinlto las correspondientes a ThinLTO. Y por último, con lld-thinlto-cache, activamos el uso del directorio de caché, allí donde su uso sea realmente necesario y acelere el proceso de compilación. A partir de LLD 11, sólo se admiten opciones de un guión que sean compatibles con GNU ld. Dicho de otro modo más claro, lo que en versiones anteriores es correcto, después del parámetro -Wl,:
Ahora no lo es, y se tienen que añadir dos guiones antes de la opción a pasarle a LLD.
En las que son compatibles con GNU ld, sí se puede seguir utilizando sólo un guión. Libc++ - La librería estándar de C++ de LLVM A partir de la versión 11 de Clang, he decidido incluir en el manual y en el paquete de código fuente de descarga, la nueva implementación de la librería estándar C++ proporcionada por LLVM, libc++, con soporte completo de los estándares C++11, C++14, C++17 y un soporte parcial de C++20. También se incluye libc++abi, que proporciona una implementación de bajo nivel para libc++. Para hacer uso de la misma en los procesos de compilación, establecemos la correspondiente variable de entorno antes de ejecutar el comando de configuración.
También la podemos utilizar con GCC, estableciendo las siguientes variables de entorno:
Los parámetros han sido probados en la compilación de DAR y son funcionales con este paquete. Si ejecutamos el comando ldd para comprobar las librerías contra las que está enlazado el binario ejecutable dar, veremos que tanto libc++ como libc++abi, aparecen en primer lugar, y libstdc++, aparece más abajo, como dependencia secundaria a través de las librerías contra las que está enlazado dicho binario. El ejemplo se muestra recortado hasta libstdc++.
Con PatchELF lo podemos comprobar de una manera más clara:
Pero vuelvo a repetir, si no nos queremos complicar la vida, utilicemos la librería estándar de C++ del sistema, es decir, la que proporciona GCC, pero por experimentar que no quede. Y como no podía ser de otra forma, los correspondientes alias que en este caso son funciones de bash para facilitar su uso:
En aquellos manuales que sea posible se irán incluyendo estos parámetros como opción de uso de esta librería con Clang, omitiendo el uso con GCC. Comparativa de resultados de optimización entre Clang y GCC Tomando como referencia la compilación del programa Openbox, pongo a continuación una tabla de resultados de optimización entre Clang y GCC, utilizando los mismos parámetros de optimización, que son equivalente entre sí. En esta tabla se mide el tiempo de compilación (12 procesos en paralelo) y el tamaño resultante del binario openbox, después de haberse compilado en el directorio y también después de haberlo instalado con el comando make install-strip que elimina los símbolos innecesarios para la ejecución del programa. Para medir el tiempo de compilación se sustituye el comando de compilación por el siguiente:
Y hacemos la correspondiente resta: hora de inicio (inicio.log) - hora final (final.log) = tiempo total de compilación. En compilaciones cortas, como es el caso de ésta, podemos utilizar este comando (sin volcar nada a ningún archivo) y ver el resultado en la pantalla, eso sí, tendremos que desplazarnos hacia arriba en la ventana de terminal, para buscar la hora de inicio.
En paquetes en los que la compilación es en modo silencioso por defecto, añadir el parámetro V=1 o VERBOSE=1 si se han configurado con CMake, al comando make, o -v si utilizamos el comando ninja, para poder ver los parámetros de optimización aplicados al paquete. Como siempre esto se puede automatizar hasta cierto punto, con los correspondiente alias de bash, un ejemplo con los míos, sustituir 12 por el número de núcleos o núcleos más hilos, que tenga el procesador de cada usuario. Abrimos con un editor de texto, el archivo de configuración personal, ~/.bashrc, si no existe lo creamos, y añadimos lo siguiente al final del contenido del mismo:
Y finalmente creamos un script para poder hacer la correspondiente resta. Necesitaremos el programa datediff, que forma parte del paquete Dateutils. Abrimos un editor de texto y añadimos lo siguiente:
Lo guardamos con el nombre tiempo y lo instalamos en /usr/bin.
Y comprobamos que funciona bien, probando a ejecutarlo cuando terminemos de compilar un paquete.
La última comparativa de esta tabla es con las optimizaciones que suelen llevar la mayoría de paquetes por defecto (-O2). Por último, tener en cuenta que un binario más pequeño no tiene por qué equivaler a un binario más rápido. Las comparativas se han hecho con el modo de ahorro de energía de la CPU establecido en "powersave (balance performance)", y con un kernel parcheado con las funcionalidades proporcionadas por el planificador de CPU, BMQ. Como enlazador dinámico de Clang, se utiliza LLD, con todas las optimizaciones incluidas en los manuales de esta web.
Esto es con código escrito en C, pero ahora los compararemos con código escrito en C++, tomando como referencia el manual de instalación de Pekwm. El binario utilizado para la comparación es pekwm_wm.
Y en la última comparativa utilizaremos toda la artillería disponible de cada compilador, compilando ImageMagick.
Se ha omitido la optimización IPA de GCC, ya que no existe una equivalente en Clang. Enlaces http://llvm.org >> La web del proyecto LLVM. http://clang.llvm.org >> La sección de la web de LLVM dedicada a Clang. http://lld.llvm.org >> La sección de la web de LLVM dedicada a LLD. http://polly.llvm.org >> La sección de la web de LLVM dedicada a Polly. http://compiler-rt.llvm.org >> La sección de la web de LLVM dedicada a Compiler-rt. http://openmp.llvm.org >> La sección de la web de LLVM dedicada a OpenMP. http://libcxx.llvm.org >> La sección de la web de LLVM dedicada a Libc++. |