Manualinux
http://www.nvu.com http://www.gimp.org InicioPresentaciónActualizacionesManualesDescargasNoticiasAgradecimientoEnlaces

Entornos GráficosAplicaciones

DesarrolloEmuladoresInternetJuegosMultimediaSistema

Instalar Binutils desde ceroInstalar CMake desde cero

Instalar Clang desde cero

Página - 1Página - 2




Instalar Clang desde cero




Copyright

Copyright © José Luis Lara Carrascal  2012-2021   http://manualinux.eu



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
Compilar programas con Clang
Comparativa de resultados de optimización entre Clang y GCC
El estándar de lenguaje de programación predefinido de C++, a partir de Clang 6
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.

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. 

$ export LDFLAGS+=" -fuse-ld=lld"

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.

$ find . -name 'ltmain.sh' | xargs sed -i 's:|-fuse-linker-plugin:&|-fuse-ld=*:' 

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:

function lld-libtool
{
if find . -name 'ltmain.sh' | xargs grep -q '\-fuse-ld=*' ; then
 echo "el parche ya está aplicado"
else
  find . -name 'ltmain.sh' | xargs sed -i 's:|-fuse-linker-plugin:&|-fuse-ld=*:'
 echo "parche aplicado"
fi
}

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 de desarrollo 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, 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.

$ su
# sed -i 's:linker-plugin|:&-fuse-ld=*|:' /usr/share/libtool/build-aux/ltmain.sh

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.

$ export LDFLAGS+=" -Wl,--lto-partitions=2"

Donde pone 2 se indica el número de núcleos de nuestro procesador, si sólo tiene uno, no es necesario añadir esto. 

Con ThinLTO, la variable de entorno a utilizar es la siguiente:

$ export LDFLAGS+=" -Wl,--thinlto-jobs=2"

Donde pone 2 se indica el número de núcleos de nuestro procesador, si sólo tiene uno, 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.

$ su -c "install -dm777 /var/cache/thinlto"

Establecemos la correspondiente variable de entorno LDFLAGS, después de las explicadas anteriormente, antes de ejecutar el script de configuración del paquete:

$ export LDFLAGS+=" -Wl,--thinlto-cache-dir=/var/cache/thinlto"

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:

$ export LDFLAGS+=" -Wl,--thinlto-cache-policy,cache_size=20%"

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.

Opciones de configuración de la política de control del directorio de caché cuando se utiliza la optimización ThinLTO
Parámetro Descripción
prune_interval=nº|s|m|h
Define cada cuanto tiempo en segundos será escaneado el directorio de caché para buscar los archivos objeto con fecha de expiración y candidatos a ser eliminados. Esta opción evita un constante escaneo de disco con la consiguiente carga que supone para el sistema. Un valor de 0, fuerza el escaneo continuo y un valor de Nonedesactiva la política de expiración de archivos objeto basada en este parámetro. El valor predefinido es de 20 minutos. El parámetro admite segundos (s), minutos (m) y horas (h). Un ejemplo:

prune_interval=30s
prune_after=nº|s|m|h
Define el tiempo de expiración de un archivo objeto de la caché. Un valor de 0, desactiva la política de expiración de archivos objeto basada en este parámetro. El parámetro admite segundos (s), minutos (m) y horas (h). El valor predefinido es de una semana. Un ejemplo, estableciendo la fecha de expiración en un mes:

prune_after=720h
cache_size=nº% Define en porcentaje el tamaño máximo del espacio en disco libre de la partición que será destinado al directorio de caché. Un valor de 0, desactiva la política de expiración de archivos objeto basada en este parámetro. Un valor de 100 habilita todo el espacio libre disponible. Como es evidente, valores superiores a 100 no son válidos. Un ejemplo:

cache_size=20%
cache_size_bytes=
Define el tamaño máximo del directorio de caché. El parámetro admite bytes, kilobytes (k), megabytes (m) y gigabytes (g). Cualquier valor superior al real disponible de espacio libre en la partición, será reducido a este último. Un valor de 0, desactiva la política de expiración de archivos objeto basada en este parámetro. Un ejemplo, con un tamaño de directorio de 2 GB:

cache_size_bytes=2g
cache_size_files= Define el número máximo de archivos objeto que contendrá el directorio de caché. Un valor de 0, desactiva la política de expiración de archivos objeto basada en este parámetro. El valor por defecto es 1000000. Un ejemplo:

cache_size_files=500000

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.

$ export LDFLAGS+=" -Wl,--thinlto-cache-policy,prune_interval=30s:prune_after=720h:cache_size=20%"

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.

$ export LDFLAGS+=" -Wl,--thinlto-cache-policy,prune_after=720h:cache_size_bytes=2g"

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.

$ export LDFLAGS+=" -Wl,-plugin-opt,cache-dir=/var/cache/thinlto"

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.

$ export LDFLAGS+=" -Wl,--lto-aa-pipeline=basic-aa -Wl,--lto-newpm-passes=loweratomic"

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:

$ export LDFLAGS+=" -Wl,--lto-aa-pipeline=globals-aa -Wl,--lto-newpm-passes=pgo-memop-opt"

Para saber todas las opciones posibles de LLD, ejecutamos el siguiente comando:

$ ld.lld --help

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:

alias clang-lld="export LDFLAGS+=' -fuse-ld=lld'"

lld-lto () { export LDFLAGS+=" -Wl,--lto-partitions="$(getconf _NPROCESSORS ONLN)" ; }

lld--thinlto () { export LDFLAGS+=" -Wl,--thinlto-jobs="$(getconf _NPROCESSORS ONLN)" ; }

alias lld--thinlto-cache="export LDFLAGS+=' -Wl,--thinlto-cache-dir=var/cache/thinlto -Wl,--thinlto-cache-policy,prune_after=720h:cache_size_bytes=2g'"

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.0.0, 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,:

$ export LDFLAGS+=" -Wl,-lto-partitions=2"

Ahora no lo es, y se tienen que añadir dos guiones antes de la opción a pasarle a LLD.

$ export LDFLAGS+=" -Wl,--lto-partitions=2"

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 y C++14, y un soporte parcial de C++17. 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.

En lugar de utilizar el nombre de la ruta de instalación, utilizamos el comando llvm-config, para así, no tener que cambiar dichar ruta en los alias que tengamos, cada vez que actualicemos el compilador a otra versión del mismo.

$ export CXXFLAGS+=" -stdlib=libc++ -I$(llvm-config --includedir)/c++/v1"
$ export LDFLAGS+=" -L$(llvm-config --libdir)"

Por norma general, los parámetros con el prefijo '-I' se suelen establecer con la variable de entorno CPPFLAGS, pero en este caso, si queremos que no se produzcan errores en la detección de las dependencias de un paquete, tendremos que incluirlo en la variable de entorno CXXFLAGS, ya que por defecto, Clang buscará las cabeceras de C++ de GCC, que son con las que se ha compilado e instalado el programa.

La variable de entorno LDFLAGS se debe de establecer para que el enlazador dinámico, encuentre la librería correspondiente, ya sea LLD o GNU gold. Si hubiera algún problema específico de enlazado en algún paquete, podemos utilizar también la siguiente variable, más amplia:

$ export LDFLAGS+=" -L$(llvm-config --libdir) -lc++ -lc++abi"

También la podemos utilizar con GCC, estableciendo las siguientes variables de entorno:

$ export CXXFLAGS+=" -nostdinc++ -I$(llvm-config --includedir)/c++/v1"
$ export LDFLAGS+=" -nodefaultlibs -L$(llvm-config --libdir) -lc++ -lc++abi -lm -lc -lgcc_s"

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++.

[jose@localhost bin]$ ldd dar
        linux-vdso.so.1 (0x00007ffcec7b9000)
        libc++.so.1 => /opt/llvm12/lib64/libc++.so.1 (0x00007f2c6e045000)
        libc++abi.so.1 => /opt/llvm12/lib64/libc++abi.so.1 (0x00007f2c6e005000)
        libm.so.6 => /lib64/libm.so.6 (0x00007f2c6dec0000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f2c6dcca000)
        libgcc_s.so.1 => /opt/gcc10/lib64/libgcc_s.so.1 (0x00007f2c6dcb0000)
        libdar64.so.6000 => /tmp/dar/usr/lib64/libdar64.so.6000 (0x00007f2c6da2c000)
        libcurl.so.4 => /usr/lib64/libcurl.so.4 (0x00007f2c6d9a7000)
        libgpgme.so.11 => /usr/lib64/libgpgme.so.11 (0x00007f2c6d94b000)
        libassuan.so.0 => /usr/lib64/libassuan.so.0 (0x00007f2c6d92b000)
        libthreadar.so.1000 => /usr/lib64/libthreadar.so.1000 (0x00007f2c6d91d000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f2c6d8fd000)
        librsync.so.2 => /usr/lib64/librsync.so.2 (0x00007f2c6d8f0000)
        libattr.so.1 => /lib64/libattr.so.1 (0x00007f2c6d8e8000)
        libgcrypt.so.20 => /usr/lib64/libgcrypt.so.20 (0x00007f2c6d784000)
        libgpg-error.so.0 => /usr/lib64/libgpg-error.so.0 (0x00007f2c6d761000)
        liblzma.so.5 => /lib64/liblzma.so.5 (0x00007f2c6d730000)
        liblzo2.so.2 => /usr/lib64/liblzo2.so.2 (0x00007f2c6d6ee000)
        libbz2.so.1.0 => /lib64/libbz2.so.1.0 (0x00007f2c6d6d6000)
        libz.so.1 => /lib64/libz.so.1 (0x00007f2c6d4b6000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007f2c6d4b1000)
        libcap.so.2 => /lib64/libcap.so.2 (0x00007f2c6d4aa000)
        libsocket.so => /usr/lib64/libsocket.so (0x00007f2c6d4a5000)
        librt.so.1 => /lib64/librt.so.1 (0x00007f2c6d49c000)
        libatomic.so.1 => /opt/gcc10/lib64/libatomic.so.1 (0x00007f2c6d493000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f2c6e186000)
        libstdc++.so.6 => /opt/gcc10/lib/../lib64/libstdc++.so.6 (0x00007f2c6d27f000)

Con PatchELF lo podemos comprobar de una manera más clara:

[jose@localhost bin]$ patchelf --print-needed dar
libdar64.so.6000
libcurl.so.4
libgpgme.so.11
libassuan.so.0
libthreadar.so.1000
libpthread.so.0
librsync.so.2
libattr.so.1
libgcrypt.so.20
libgpg-error.so.0
liblzma.so.5
liblzo2.so.2
libbz2.so.1.0
libz.so.1
libdl.so.2
libcap.so.2
libsocket.so
libc++.so.1
libc++abi.so.1
libm.so.6
libgcc_s.so.1
libc.so.6

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:

optclang-cxx () { export CXXFLAGS+=" -stdlib=libc++ -I$(llvm-config --includedir)/c++/v1" ; \
export LDFLAGS+=" -L$(llvm-config --libdir)" ; }

optgcc-cxx () { export CXXFLAGS+=" -nostdinc++ -I$(llvm-config --includedir)/c++/v1" ; \
export LDFLAGS+=" -nodefaultlibs -L$(llvm-config --libdir) -lc++ -lc++abi -lm -lc -lgcc_s" ; }


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.



Compilar programas con Clang  Bloc de Notas

Desde que existe este manual, la variable de entorno a utilizar de uso de compilador ha sido siempre ésta, que es la que he utilizado siempre para compilar los programas y librerías de mi sistema. Como siempre establecía un RPATH, no tenía en cuenta el uso de Clang en un sistema normal de usuario, y reducía la variable a establecer al siguiente comando, para reducir espacio a la hora de escribirla:

$ export {CC,CXX}=clang

El problema principal de recortar esta variable es que, en la compilación de código escrito en C++, si utilizamos clang en lugar de clang++, no se producirá un enlazado contra la librería libstdc++ del sistema ni contra la librería libm, y se producirá un error en el proceso de compilación. Por lo que, a partir de ahora, la variable de entorno de uso de compilador en todos los manuales de la web, incluido éste, es la siguiente:

$ export CC=clang CXX=clang++

Los usuarios que utilicen Ccache como compilador caché de GCC, también pueden utilizar Clang, estableciendo previamente al proceso de configuración del paquete, la correspondiente variable de entorno.

$ export CCACHE_COMPILER=clang

Aunque yo, personalmente, prefiero utilizar ésta, que incluye el nombre del compilador en el proceso de compilación, y es, la que se irá incluyendo en los manuales, en sustitución de la anterior descrita.

$ export CC="ccache clang" CXX="ccache clang++"

A continuación pongo el ejemplo de compilación de NomNom, utilizando GCC, Clang, Clang con Polly y LTO, y Clang con Ccache. Las optimizaciones aplicadas son las del procesador que tengo en mi ordenador.

Extracción y Configuración con GCC

$ tar Jxvf nomnom-0.3.1.tar.xz
$ cd nomnom-0.3.1
$ export {C,CXX}FLAGS='-O3 -march=znver2 -mtune=znver2'
$ export PATH=/usr/local/lib64/qt4/bin:$PATH
$ export LDFLAGS="-Wl,-rpath,/opt/gcc10/lib64 -lstdc++"
$ ./configure --disable-dependency-tracking --prefix=/usr

Extracción y Configuración con Clang

$ tar Jxvf nomnom-0.3.1.tar.xz
$ cd nomnom-0.3.1
$ export {C,CXX}FLAGS='-O3 -march=znver2 -mtune=znver2'
$ export CC=clang CXX=clang++
$ export PATH=/usr/local/lib64/qt4/bin:$PATH
$ export LDFLAGS="-Wl,-rpath,/opt/gcc10/lib64 -lstdc++"
$ ./configure --disable-dependency-tracking --prefix=/usr

Extracción y Configuración con Clang, Polly y LTO

$ tar Jxvf nomnom-0.3.1.tar.xz
$ cd nomnom-0.3.1
$ export {C,CXX}FLAGS='-O3 -march=znver2 -mtune=znver2'
$ export {C,CXX}FLAGS+=" -O3 -mllvm -polly -mllvm -polly=vectorizer=stripmine -flto"
$ export CC=clang CXX=clang++
$ export PATH=/usr/local/lib64/qt4/bin:$PATH
$ export LDFLAGS="-Wl,-rpath,/opt/gcc10/lib64 -lstdc++"
$ ./configure --disable-dependency-tracking --prefix=/usr

Extracción y Configuración con Clang y Ccache

$ tar Jxvf nomnom-0.3.1.tar.xz
$ cd nomnom-0.3.1
$ export {C,CXX}FLAGS='-O3 -march=znver2 -mtune=znver2'

$ export CCACHE_COMPILER=clang
$ export PATH=/usr/local/lib64/qt4/bin:$PATH
$ export LDFLAGS="-Wl,-rpath,/opt/gcc10/lib64 -lstdc++"
$ ./configure --disable-dependency-tracking --prefix=/usr

Compilación con GCC

[jose@localhost nomnom-0.3.1]$ make
make  all-recursive
make[1]: se ingresa al directorio `/home/jose/descargas/nomnom-0.3.1'
Making all in src
make[2]: se ingresa al directorio `/home/jose/descargas/nomnom-0.3.1/src'
/usr/local/lib64/qt4/bin/uic -o ui_MainWindow.h ../src/rc/MainWindow.ui
make  all-am
make[3]: se ingresa al directorio `/home/jose/descargas/nomnom-0.3.1/src'
  CXX    naboutdialog.o
g++ -DHAVE_CONFIG_H -I. -I..  -I../src -I../src/i -DDATADIR='"/usr/share"' -DQT_SHARED -I/usr/local/lib64/qt4/include -I/usr/local/lib64/qt4/include/QtCore   -DQT_SHARED -I/usr/local/lib64/qt4/include -I/usr/local/lib64/qt4/include/QtGui -I/usr/local/lib64/qt4/include/QtCore   -DQT_SHARED -I/usr/local/lib64/qt4/include -I/usr/local/lib64/qt4/include/QtCore     -O3 -march=znver2 -mtune=znver2 -c -o naboutdialog.o `test -f 'about/naboutdialog.cpp' || echo './'`about/naboutdialog.cpp
  CXX    ndetectdialog.o

Compilación con Clang

[jose@localhost nomnom-0.3.1]$ make
make  all-recursive
make[1]: se ingresa al directorio `/home/jose/descargas/nomnom-0.3.1'
Making all in src
make[2]: se ingresa al directorio `/home/jose/descargas/nomnom-0.3.1/src'
/usr/local/lib64/qt4/bin/uic -o ui_MainWindow.h ../src/rc/MainWindow.ui
make  all-am
make[3]: se ingresa al directorio `/home/jose/descargas/nomnom-0.3.1/src'
  CXX    naboutdialog.o
clang++ -DHAVE_CONFIG_H -I. -I..  -I../src -I../src/i -DDATADIR='"/usr/share"' -DQT_SHARED -I/usr/local/lib64/qt4/include -I/usr/local/lib64/qt4/include/QtCore   -DQT_SHARED -I/usr/local/lib64/qt4/include -I/usr/local/lib64/qt4/include/QtGui -I/usr/local/lib64/qt4/include/QtCore   -DQT_SHARED -I/usr/local/lib64/qt4/include -I/usr/local/lib64/qt4/include/QtCore     -O3 -march=znver2 -mtune=znver2 -c -o naboutdialog.o `test -f 'about/naboutdialog.cpp' || echo './'`about/naboutdialog.cpp

Compilación con Clang, Polly y LTO

[jose@localhost nomnom-0.3.1]$ make
make  all-recursive
make[1]: Entering directory '/home/jose/descargas/nomnom-0.3.1'
Making all in src
make[2]: Entering directory '/home/jose/descargas/nomnom-0.3.1/src'
/usr/local/lib64/qt4/bin/uic -o ui_MainWindow.h ../src/rc/MainWindow.ui
make  all-am
make[3]: Entering directory '/home/jose/descargas/nomnom-0.3.1/src'
  CXX    naboutdialog.o
clang++ -DHAVE_CONFIG_H -I. -I..  -I../src -I../src/i -DDATADIR='"/usr/share"' -DQT_SHARED -I/usr/local/lib64/qt4/include -I/usr/local/lib64/qt4/include/QtCore  -DQT_SHARED -I/usr/local/lib64/qt4/include -I/usr/local/lib64/qt4/include/QtGui -I/usr/local/lib64/qt4/include -I/usr/local/lib64/qt4/include/QtCore  -DQT_SHARED -I/usr/local/lib64/qt4/include -I/usr/local/lib64/qt4/include/QtCore    -O3 -march=znver2 -mtune=znver2 -O3 -mllvm -polly -mllvm -polly-vectorizer=stripmine -flto -c -o naboutdialog.o `test -f 'about/naboutdialog.cpp' || echo './'`about/naboutdialog.cpp

Compilación con Clang y Ccache (se muestra el enlace simbólico g++)

[jose@localhost nomnom-0.3.1]$ make
make  all-recursive
make[1]: se ingresa al directorio `/home/jose/descargas/nomnom-0.3.1'
Making all in src
make[2]: se ingresa al directorio `/home/jose/descargas/nomnom-0.3.1/src'
/usr/local/lib64/qt4/bin/uic -o ui_MainWindow.h ../src/rc/MainWindow.ui
make  all-am
make[3]: se ingresa al directorio `/home/jose/descargas/nomnom-0.3.1/src'
  CXX    naboutdialog.o
g++ -DHAVE_CONFIG_H -I. -I..  -I../src -I../src/i -DDATADIR='"/usr/share"' -DQT_SHARED -I/usr/local/lib64/qt4/include -I/usr/local/lib64/qt4/include/QtCore   -DQT_SHARED -I/usr/local/lib64/qt4/include -I/usr/local/lib64/qt4/include/QtGui -I/usr/local/lib64/qt4/include/QtCore   -DQT_SHARED -I/usr/local/lib64/qt4/include -I/usr/local/lib64/qt4/include/QtCore     -O3 -march=znver2 -mtune=znver2 -c -o naboutdialog.o `test -f 'about/naboutdialog.cpp' || echo './'`about/naboutdialog.cpp
  CXX    ndetectdialog.o



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 (8 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:

$ date &> inicio.log; make; date &> final.log

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.

$ date; make; date

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 8 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:

alias makej1="date +"%H:%M:%S" &> inicio.log; make V=1; date +"%H:%M:%S" &> final.log"
alias makej="date +"%H:%M:%S" &> inicio.log; make V=1 -j8; date +"%H:%M:%S" &> final.log"
alias cmakej1="date +"%H:%M:%S" &> inicio.log; make VERBOSE=1; date +"%H:%M:%S" &> final.log"
alias cmakej="date +"%H:%M:%S" &> inicio.log; make VERBOSE=1 -j8; date +"%H:%M:%S" &> final.log"
alias ninjaj1="date +"%H:%M:%S" &> inicio.log; ninja -v -j1; date +"%H:%M:%S" &> final.log"
alias ninjaj="date +"%H:%M:%S" &> inicio.log; ninja -v -j8; date +"%H:%M:%S" &> final.log"


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:

#!/bin/sh

date1=`cat inicio.log`
date2=`cat final.log`

ddiff $date1 $date2 -f '%Hh %Mm %Ss'


Lo guardamos con el nombre tiempo y lo instalamos en /usr/bin.

$ su -c "install -m755 tiempo /usr/bin"

Y comprobamos que funciona bien, probando a ejecutarlo cuando terminemos de compilar un paquete.

[jose@localhost dateutils-0.4.8]$ tiempo
0h 0m 10s

La última comparativa de esta tabla es sin optimizaciones adicionales, sólo se utilizan las proporcionadas por el paquete (-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 "performance", y con un kernel parcheado con las funcionalidades proporcionadas el planificador de CPU, CacULE.

Comparativa de resultados de optimización entre Clang 13 y GCC 11, con código escrito en C
Parámetros aplicados Tiempo de compilación Tamaño del binario compilado Tamaño del binario instalado
Clang+O3+CPU+Polly+ThinLTO 7" 684 KB. 456 KB.
GCC+O3+CPU+Graphite+LTO 7"
712 KB. 504 KB.

Clang+O3+CPU+Polly+LTO 8"
660 KB. 460 KB.
GCC+O3+CPU+Graphite+LTO 7"
712 KB. 504 KB.

Clang+O3+CPU+Polly 5" 604 KB. 400 KB.
GCC+O3+CPU+Graphite 4" 644 KB. 440 KB.

Clang+O3+CPU+LTO 7" 656 KB. 456 KB.
GCC+O3+CPU+LTO 7" 712 KB. 504 KB.

Clang+O3+CPU 5" 604 KB. 400 KB.
GCC+O3+CPU 4" 644 KB. 440 KB.

Clang+O3 5" 608 KB. 404 KB.
GCC+O3 4" 640 KB. 436 KB.

Clang 5" 1,8 MB. 392 KB.
GCC 5" 1,9 MB. 404 KB.
De color amarillo las celdas con los mejores resultados.

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.

Comparativa de resultados de optimización entre Clang 13 y GCC 11, con código escrito en C++
Parámetros aplicados Tiempo de compilación Tamaño del binario compilado Tamaño del binario instalado
Clang+O3+CPU+Polly+ThinLTO 9" 844 KB. 724 KB.
GCC+O3+CPU+Graphite+LTO 8"
804 KB. 680 KB.

Clang+O3+CPU+Polly+LTO 14"
808 KB. 704 KB.
GCC+O3+CPU+Graphite+LTO 8"
804 KB. 680 KB.

Clang+O3+CPU+Polly 8" 888 KB. 720 KB.
GCC+O3+CPU+Graphite 8" 1,0 MB. 848 KB.

Clang+O3+CPU+LTO 13" 808 KB. 704 KB.
GCC+O3+CPU+LTO 8" 804 KB. 680 KB.

Clang+O3+CPU 7" 892 KB. 724 KB.
GCC+O3+CPU 8" 1,0 MB. 848 KB.

Clang+O3 7" 892 KB. 724 KB.
GCC+O3 8" 1,0 MB. 832 KB.

Clang 8" 9,4 MB. 700 KB.
GCC 11" 20,9 MB. 732 KB.
De color amarillo las celdas con los mejores resultados.

Y en la última comparativa utilizaremos toda la artillería disponible de cada compilador, compilando ImageMagick.

Comparativa de resultados de optimización entre Clang 13 y GCC 11, en la compilación de ImageMagick
Parámetros aplicados Tiempo de compilación Tamaño del directorio de  compilación Tamaño del paquete instalado
Clang+O3+CPU+Polly+OpenMP+ThinLTO+LLD 35" 124,9 MB. 26,5 MB.
GCC+O3+CPU+Graphite+OpenMP+LTO+LD 47"
135,2 MB. 28,0 MB.
De color amarillo las celdas con los mejores resultados.

Se ha omitido la optimización IPA de GCC, ya que no existe una equivalente en Clang.



El estándar de lenguaje de programación predefinido de C++, a partir de Clang 6

A partir de Clang 6, el estándar de lenguaje de programación predefinido de C++, pasa a ser el mismo que utiliza GCC desde su versión 6, es decir, GNU++14, que es la variante GNU de C++14. En paquetes de código fuente que no estén actualizados a este estándar concreto (el proceso de compilación producirá errores), tendremos que establecer la correspondiente variable de entorno CXXFLAGS, antes de ejecutar el comando de configuración del paquete en cuestión, para que se use el estándar anterior.

$ export CXXFLAGS+=" -std=gnu++98"



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++.


Foro Galería Blog


Página - 1Página - 2

Actualizado el 09-10-2021

Instalar Clang desde cero

Instalar Binutils desde ceroInstalar CMake desde cero