Cómo mejorar el rendimiento del GCC para x86

La gente dice que el conjunto de compiladores GCC (Colección de Compiladores GNU) no puede generar código eficiente en comparación con otros compiladores sujetos a derechos. ¿Es un mito o una realidad? Intentaremos ver cómo es la cuestión con el GCC. Entonces, ¿cómo podemos hacer que el compilador GCC produzca código más eficiente? Describiremos algunas sugerencias opcionales para compilar "C", "C++" y "Fortran" en plataforma Linux x86 que ayudan a mejorar el rendimiento del GCC. Deberían ser útiles para aquellos clientes y desarrolladores que necesitan un mayor rendimiento, pero que por diversos motivos no usan compiladores sujetos a derechos.

¿Cuáles son las opciones predeterminadas del GCC?

(1) El nivel de optimización predeterminado del GCC es "-O0". El manual del GCC dice "Cuando no hay ninguna opción de optimización, el objetivo del compilador es reducir el costo de compilación y hacer que la depuración produzca los resultados esperados". El comportamiento predeterminado deriva en un rendimiento muy bajo, y no se recomienda usar el comando "gcc" cuando se compila en modo release.
El GCC no reconoce arquitecturas a menos que se agregue la opción "-march=native". De manera predeterminada, el GCC transfiere las opciones que se establecieron en su configuración. Veamos cómo se configuró gcc:

gcc -v
"Configured with: [PATH]/configure … --with-arch=corei7 --with-cpu=corei7…"

Esto quiere decir que el GCC agregará "-march=corei7" a las opciones de línea de comandos.
La mayoría de los compiladores GCC para x86 (predeterminado en Linux de 64 bits) agregan: "-mtune=generic -march=x86-64" a las opciones de la línea de comandos, si se configuraron de la manera correspondiente. Siempre se puede ejecutar lo siguiente para comprobar las opciones que transfiere el controlador de GCC y las opciones internas:

echo "int main {return 0;}" | gcc [OPTIONS] -x c -v -Q -

Por ejemplo, este es uno de los comandos que se usa con habitualidad:

gcc -O2 test.c

Este comando compila "test.c" sin optimizaciones para ninguna arquitectura específica y puede conducir a pérdidas de rendimiento considerables (en comparación con el código ajustado específicamente). La mayoría de estas pérdidas se debe a la reducción (o desactivación) de la vectorización y una planificación ineficiente del código.
Para obtener un rendimiento superior, se debe usar el siguiente comando:

gcc -O2 test.c -march=native

¡Es importante definir la arquitectura! La única excepción es cuando el programa vinculado dedica la mayor parte del tiempo de ejecución a funciones de GLIBC, ya que la mayoría de ellas determina código óptimo para el funcionamiento en la arquitectura actual mientras se ejecutan. Vale la pena observar que algunas funciones de GLIBC estáticas de uso frecuente no tienen especialización de arquitectura. La vinculación dinámica conviene si se desea una GLIBC más rápida.

(2) De manera predeterminada, la mayoría de los compiladores GCC para x86 en modo de 32 bits usan un modelo de punto flotante x87 configurado sin "mfpmath=sse". Solo si el GCC fue configurado "--with-mfpmath=sse", como:

gcc -v
"Configured with: [PATH]/configure … --with-mfpmath=sse…"

usa de manera predeterminada un modelo de punto flotante SSE. En los demás casos hay que especificar "-mfpmath=sse" para mejorar el rendimiento en punto flotante. Este comando que se usa con frecuencia puede perjudicar considerablemente el rendimiento del código en operaciones de punto flotante

   gcc -O2 -m32 test.c

Para mejorar el rendimiento de prueba, se puede compilar con:

   gcc -O2 -m32 test.c -mfpmath=sse

¡Agregar "-mfpmath=sse" es importante en modo de 32 bits! La única excepción es cuando el compilador se configuró "--with-mfpmath=sse".


¿32 bits o 64 bits?

El modo de 32 bits se emplea para reducir el uso de memoria, y como consecuencia se reduce el tiempo de acceso a la memoria (ya que se pueden ubicar más datos en los cachés).
En el modo de 64 bits, la cantidad de registros disponibles aumenta de 6 a 14 generales y de 8 a 16 XMM. Además, todas las arquitecturas x86 de 64 bits tienen extensión SSE2 predeterminada (no es necesario agregar "-mfpmath=sse").
Se recomienda usar 64 bits para aplicaciones de computación de alto rendimiento (HPC) y 32 bits para aplicaciones que se ejecutarán en teléfonos y tabletas electrónicas.


¿Cómo se puede lograr el máximo rendimiento?

El compilador GCC proporciona muchas oportunidades de realizar pruebas con el fin de alcanzar un mayor rendimiento. Abajo incluimos una tabla resumen con recomendaciones y predicciones para procesadores Intel® Atom™ e Intel® Core™ i7 2.ª Generación en comparación con únicamente la opción "-O2" sobre la base de resultados del GCC 4.7, suponiendo que el GCC se configuró para x86-64 genérico.

Predicción de mejora de rendimiento para aplicaciones de uso habitual en tabletas y teléfonos relativa a "-O2" (solo en el modo de 32 bits, que es el común para aplicaciones de teléfonos y tabletas):

-m32 -mfpmath=sse ~5%
-m32 -mfpmath=sse -Ofast -flto ~36%
-m32 -mfpmath=sse -Ofast -flto -march=native ~40%
-m32 -mfpmath=sse -Ofast -flto -march=native -funroll-loops ~43%

Pronóstico de mejora de rendimiento en aplicaciones HPC con relación a "-O2" (en modo de 32 bits):

-m32 -mfpmath=sse ~4%
-m32 -mfpmath=sse -Ofast -flto ~21%
-m32 -mfpmath=sse -Ofast -flto -march=native ~25%
-m32 -mfpmath=sse -Ofast -flto -march=native -funroll-loops ~24%

Pronóstico de mejora de rendimiento en aplicaciones HPC con relación a "-O2" (en modo de 64 bits):

-m64 -Ofast -flto ~17%
-m64 -Ofast -flto -march=native ~21%
-m64 -Ofast -flto -march=native -funroll-loops ~22%

La ventaja del modo de 64 bits respecto del de 32 bits en aplicaciones HPC en "-O2 -mfpmath=sse" es de ~5 %.
Hay que tener presente que todas las cifras incluidas en este artículo provienen de predicciones basadas en ciertos conjuntos de bancos de pruebas.

Abajo incluimos un breve resumen de las opciones utilizadas. La lista completa de opciones y las descripciones se pueden consultar en http://gcc.gnu.org/onlinedocs/gcc-4.7.1/gcc/Optimize-Options.html"

  • "-Ofast" same as "-O3 -ffast-math" posibilita optimizaciones de alto nivel y optimizaciones agresivas en cálculos aritméticos (tales como reasociaciones de punto flotante).
  • "-flto" posibilita optimizaciones de tiempo de vinculación.
  • "-m32" cambia a modo de 32 bits.
  • "-mfpmath=sse" habilita el uso de registros XMM en instrucciones de punto flotante (en lugar de pila en modo x87).
  • "-funroll-loops" habilita el desenrollamiento de bucles.
Para obtener información más completa sobre las optimizaciones del compilador, consulte nuestro Aviso de optimización.