Guía de Compilación Optimizada
1.
Introducción
¿Qué son CFLAGS y CXXFLAGS?
CFLAGS y CXXFLAGS son variables de entorno usadas para decirle a la
Colección de Compiladores GNU, gcc, que tipo de parámetros usar
cuando compila código fuente. Las CFLAGS son para código escrito en C,
mientras que CXXFLAGS son para código escrito en C++.
Pueden usarse para disminuir la cantidad de mensajes de depuración
para un programa, aumentar los niveles de aviso de errores, y por
supuesto, optimizar el código producido. El
Cuaderno de GNU gcc mantiene una completa lista de opciones
disponibles y sus propósitos.
¿Cómo se usan?
CFLAGS y CXXFLAGS pueden ser usados de dos maneras. La primera,
pueden usarse por programa con los Makefiles generados por
automake.
Sin embargo, esto no debería usarse cuando instalamos paquetes
encontrados en el árbol del Portage. En su lugar, establezca sus
CFLAGS y CXXFLAGS en /etc/portage/make.conf. De
esta manera todos los paquetes se compilaran con las opciones que
especifique.
Listado de Código 1.1: CFLAGS en /etc/portage/make.conf |
CFLAGS="-march=athlon64 -O2 -pipe"
CXXFLAGS="${CFLAGS}"
|
Como puede ver, CXXFLAGS se establece para usar todas las opciones
presentes en CFLAGS. Esto es lo que deseara casi seguro. No debería
necesitar especificar opciones adicionales en CXXFLAGS nunca.
Conceptos generales
Mientras CFLAGS y CXXFLAGS pueden ser muy efectivos tomando el código
fuente para producir binarios pequeños y/o rápidos, también pueden
deteriorar la función de su código, inflar su tamaño, ralentizar su
ejecución, o incluso causar errores de compilación.
CFLAGS no es una solución mágica; no hará que su sistema corra más
rápido o sus binarios sean más pequeños automáticamente. Añadir más y
más parámetros en un intento de optimización (o "rizar") su sistema es una
receta segura para el fallo. Hay un punto en el cual permanecerá dando
vueltas.
A pesar de la jactancia que pueden encontrar en Internet, unas
variables CFLAGS y CXXFLAGS agresivas están más cerca de dañar sus
programas que de hacerles algún bien. Recuerde que la razón para la
cual existen los parámetros en primer lugar es porque están diseñadas
para usarse en sitios específicos para propósitos específicos. ¡Solo
porque una CFLAG particular sea buena para un fragmento de código no
significa que esté diseñada para compilar todo lo que quiera instalar
en su máquina¡.
¿Preparado?
Ahora que está advertido de algunos de los riesgos involucrados,
echemos un vistazo a algo sano, optimizaciones seguras para su
ordenador. Esto le será útil y lo agradecerán los desarrolladores la
próxima vez que reporte un problema en Bugzilla. (Los desarrolladores
suelen pedir que recompile un paquete con los CFLAGS mínimos para ver
si el problema persiste. Recuerde que los parámetros agresivos pueden
arruinar el código.)
2.
Optimizando
Lo básico
La meta detrás de usar CFLAGS y CXXFLAGS es crear código específico
para su sistema; debería funcionar perfectamente mientra es ligero y
rápido, si es posible. Algunas veces estás condiciones son mutuamente
excluyentes, pero nosotros jugaremos con combinaciones que sabemos que
funcionan bien. Idealmente, las mejores están disponibles para cada
arquitectura de CPU. Mencionaremos más adelante los parámetros
agresivos para que se sepa con cuales tener cuidado. No discutiremos
cada opción listada en el manual de gcc (hay cientos), pero
hablaremos de las básicas, los más comunes.
Nota:
Si no está seguro qué hace el parámetro, revise el capítulo relevante
en el
manual de gcc y si aún continúa atascado, pruebe Google, o
revise las listas de
correo de gcc.
|
-march
La primera y más importante opción es -march. Esta le dice al
compilador que código debería producirse para su arquitectura
de procesador (o arch); dice que debería producir código para
un cierto tipo de CPU. Diferentes CPUs tienen diferentes
características, soportan diferentes conjunto de instrucciones y
tienen diferentes formas de ejecutar código. El parámetro
-march mandará al compilador producir código específico para su
CPU, tomando en cuenta todas sus capacidades, características,
conjuntos de instrucciones, caprichos y demás.
A pesar que la variable CHOST en /etc/portage/make.conf
especifica la arquitectura general usada, -march también se
usa para que sus programas sean optimizados para su procesador
específico. Las arquitecturas x86 y x86-64 (entre otras) también
deberían hacer uso del parámetro -march.
¿Qué tipo de CPU tiene? Para averiguarlo, ejecute la siguiente orden:
Listado de Código 2.1: Examinando la información de la CPU |
$ cat /proc/cpuinfo
|
Ahora veamos -march en acción. Este ejemplo es para un viejo
Pentium III:
Listado de Código 2.2: /etc/portage/make.conf: Pentium III |
CFLAGS="-march=pentium3"
CXXFLAGS="${CFLAGS}"
|
Aquí hay otro para una CPU de 64-bit AMD:
Listado de Código 2.3: /etc/portage/make.conf: AMD:64 |
CFLAGS="-march=athlon64"
CXXFLAGS="${CFLAGS}"
|
Si todavía no está seguro qué tipo de CPU tiene, tal vez quiera usar
la opción -march=native. Al usarla, GCC detectará el procesador
y automáticamente usará las opciones apropiadas.Sin embargo, no
use esta opción si la intención es ¡compilar paquetes para un CPU
diferente!
De manera que, si está compilando paquetes en una computadora, pero
piensa ejecutarlos en una computadora diferente (usando, por ejemplo,
una computadora rápida para construir paquetes para una máquina más
vieja y lenta), entonces no use la opción
-march=native. La palabra "native" significa que el código
producido podrá ejecutarse solamente en ese tipo de CPU. Las
aplicaciones construidas con -march=native en un CPU AMD Athlon
64 CPU no podrán ejecutarse en un CPU VIA C3 más antiguo.
También están disponibles los parámetros -mcpu y
-mtune. Cada uno de ellos solo se usará cuando no haya
otra opción -march disponible. Ciertas arquitecturas de
procesador pueden requerir -mtune o incluso de
-mcpu. Desgraciadamente, el comportamiento de gcc no es
muy consistente con la manera que cada parámetro se comporta de una
arquitectura a la otra.
En CPUs x86 y x86-64, -mcpu generará código específico para esta
CPU usando todas sus instrucciones disponibles y el ABI correcto; no
tendrá compatibilidad hacia atrás para CPUs antiguas/diferentes. Si no
necesita ejecutar código en otro sitio que en el sistema que está
corriendo Gentoo, continúe para usar -march. Solo debería
considerar usar -mtune cuando necesite generar código para CPUs
antiguas como i386 e i486. -mtune produce un código más
genérico que -march; aunque afinará el código para cierto CPU,
no tendrá en cuenta los conjuntos de instrucciones disponibles y ABI. No
use -mcpu en sistemas x86 o x86-64, ya que está obsoleto para
estas arquitecturas.
Solo CPUs no x86/x86-64 (como Sparc, Alpha y PowerPC) pueden requerir
-mtune o -mcpu en lugar de -march. En estas
arquitecturas, -mtune/-mcpu algunas veces se comportará
como -march en (x86/x86-64) ... pero con un nombre distinto. De
nuevo, el comportamiento de gcc y los nombres de los parámetros
no es consistente entre arquitecturas, así que asegúrese de revisar el
manual de gcc para determinar cual de
ellos se ajusta a su sistema.
Nota:
Para más sugerencias de configuraciones de
-march/-mtune/ -mcpu, por favor lea el capítulo 5
de la Guía de Instalación de Gentoo
apropiada a su arquitectura. También, lea el manual de
gcc listado en la página
opciones específicas por arquitectura, con
explicaciones más detalladas sobre las diferencias entre
-march, -mcpu, y -mtune.
|
-O
Lo siguiente es la variable -O. Controla el total de niveles de
optimización. Hace que la compilación de código tome algo más de
tiempo, y puede tomar mucha más memoria, especialmente al incrementar
el nivel de optimización.
Hay cinco configuraciones para -O: -O0, -O1,
-O2, -O3 y -Os. Debería usar solamente una de
ellas en /etc/portage/make.conf.
Con la excepción de -O0, la configuración de -O activa
varios parámetros adicionales, así que asegúrese de leer el capítulo
del manual de gcc en
opciones de optimización para aprender cuales
parámetros se activan en cada nivel -O, así como algunas
explicaciones sobre lo que hacen.
Examinemos cada nivel de optimización:
-
-O0: Este nivel (la letra "O" seguida de un cero)
desconecta por completo la optimización y es el predeterminado si
no se especifica ningún nivel -O en CFLAGS o CXXFLAGS. El
código no será optimizado. Esto, normalmente, no es lo que se
desea.
-
-O1: Este es el nivel de optimización más básico. El
compilador intentará producir un código rápido y pequeño sin tomar
mucho tiempo de compilación. Es bastante básico, pero conseguirá
acabar el trabajo siempre.
-
-O2: Un paso por encima de -O1. Este es el nivel
recomendado de optimización, a no ser que tenga necesidades
especiales. -O2 activará unos pocos parámetros añadidos a
los que se activan con -O1. Con -O2, el compilador
intentará aumentar el rendimiento del código sin comprometer el
tamaño y sin tomar mucho más tiempo de compilación.
-
-O3: Este es el más alto nivel de optimización posible, y
también el más arriesgado. Tomará muchísimo tiempo compilar su
código con esta opción, y de hecho, no debería usarse a través de
todo el sistema con gcc 4.x. El comportamiento de
gcc ha cambiado significativamente desde la versión 3.x,
donde con -O3 se había demostrado producir código con
tiempos de ejecución marginalmente menores sobre -O2, pero
este ya no es el caso con gcc 4.x. Compilar todos sus
paquetes con -O3 resultará en grandes binarios que
requerirán mucha memoria e incrementará significativamente los
extraños fallos de compilación o los comportamientos inesperados
de los programas (incluidos los errores). Las desventajas
compensan las ventajas. No se recomienda usar -O3 con
gcc 4.x.
-
-Os: Este nivel optimizará su código para el tamaño. Activa
todas las opciones de -O2 que no aumenten el tamaño del
código generado. Es útil para máquinas con capacidad limitada de
disco y/o con CPUs con poca caché. Sin embargo, puede causar
algunos problemas porque es filtrado por muchos ebuilds del
árbol. No aconsejamos usar -Os.
Como se comentó anteriormente, -O2 es el nivel de optimización
recomendado. Si un paquete muestra errores de compilación, asegúrese
que no está usando -O3. Como opción de marcha atrás, pruebe
configurando CFLAGS y CXXFLAGS a un nivel de optimización inferior,
como -O1 o incluso -O0 -g2 -ggdb (para reportar errores
y comprobar posibles problemas) y recompile el paquete.
-pipe
-pipe es un parámetro común. Realmente no tiene efecto sobre el
código que se produce, pero hace que el proceso de compilación sea más
rápido. Indica al compilador que use tuberías en lugar de archivos
temporales durante los diferentes estados de compilación, lo cual usa
más memoria. En sistemas con poca memoria, el proceso de gcc podría
ser terminado. En este caso no use este parámetro.
-fomit-frame-pointer
Esta es un parámetro muy común diseñada para reducir el tamaño del
código generado. Está activado para todos los niveles de -O
(excepto -O0) en arquitecturas donde no interfiera con el
depurado (como x86-64), pero puede hacer falta activarlo uno mismo,
añadiéndolo a sus parámetros. Aunque el manual de GNU gcc no
especifica todas las arquitecturas en las que está activado al usar
-O, hay que activarlo explícitamente en un x86. Sin embargo,
usando este parámetro hará que la depuración sea de dura a imposible.
En particular, provoca que localizar problemas en aplicaciones
escritas en Java sea mucho más complicada, aunque Java no es el único
código afectado al usar este parámetro. Así, aunque este parámetro
puede ayudar, el depurado será duro; los "backtraces" en particular
serán inútiles. Sin embargo, si no planea hacer muchas depuraciones y
no tiene añadida ninguna otra CFLAG relacionada con la depuración como
-ggdb (y no está instalando paquetes con la variable USE
debug), entonces intente usar -fomit-frame-pointer.
Importante:
No combine -fomit-frame-pointer con el parámetro de
nombre similar -momit- leaf-frame-pointer. El uso de este
último está desaconsejado, ya que -fomit-frame-pointer ya hace
el trabajo apropiado. Es más, -momit-leaf-frame-pointer ha
demostrado que impacta negativamente en el rendimiento del código.
|
-msse, -msse2, -msse3, -mmmx, -m3dnow
Estos parámetros activan los conjuntos de instrucciones SSE,
SSE2, SSE3, MMX, and 3DNow! para
arquitecturas x86-64. Son útiles principalmente en multimedia, juegos
y otras tareas intensivas de computación en punto flotante, aunque
también contienen muchos otros realces matemáticos. Estos conjuntos de
instrucciones se encuentran en las más modernas CPUs.
Importante:
Asegúrese de verificar si su CPU los soporta ejecutando cat
/proc/cpuinfo. La salida incluirá cualquier conjunto de
instrucciones adicionales. Note que pni es solo otro nombre
para SSE3.
|
Normalmente no necesita añadir ninguno de estos parámetros a
/etc/portage/make.conf mientras esté usando la
-march correcta (por ejemplo, -march= nocona implica
-msse3). Algunas excepciones notables son los CPUs nuevos VIA y
AMD64 que soportan instrucciones no implicadas por -march (como
SSE3). Para CPUs como estos necesitará habilitar parámetros
adicionales donde sea apropiado después de verificar la salida de
cat /proc/cpuinfo.
Nota:
Revise la
lista de parámetros
específicos para x86 y x86-64 para ver cuales de estos conjuntos de
instrucciones son activados por la propia configuración del tipo de
CPU. Si una instrucción está listada, entonces no necesita
especificarla; se activará al usar la configuración de -march
apropiada.
|
3.
PUFs de Optimización
¡Pero tengo un mejor rendimiento con -funroll-loops -fomg-optimize!
No, solo piensa que lo hace porque alguien le ha convencido que
más parámetros es mejor. Los parámetros agresivos solo dañaran sus
aplicaciones cuando use un sistema completo. Incluso el
manual de gcc dice que usar
-funroll-loops y -funroll-all-loops crea código más
grande y que corre más lento. Aún por algunas razones, estos dos
parámetros, junto con -ffast-math, -fforce-mem,
-fforce-addr, y similares, continúan siendo muy populares entre
pardillos que quieren saber más que nadie.
La verdad es que son parámetros peligrosamente agresivos. Eche un
vistazo a los Foros de
Gentoo y Bugzilla para
ver que hacen estas variables: ¡nada bueno!
No necesita usar estos parámetros globalmente en CFLAGS o
CXXFLAGS. Solo dañarán el rendimiento. Puede sonarle como que tiene un
sistema avanzado de alto rendimiento, pero no hará más que inflar su
código y marcar sus informes de errores como INVALID o WONTFIX.
No necesita parámetros peligrosos como estas. No las
use. Quédese con las básicas: -march, -O, y
-pipe.
¿Qué pasa con los niveles -O mayores que 3?
Algunos usuarios alardean que obtienen mejor rendimiento usando
-O4, -O9, y así, pero la realidad es que niveles de
-O mayores que 3 no tienen efecto. El compilador puede aceptar
CFLAGS como -O4, pero realmente no hace nada con el. Solo
realiza la optmización para -O3, nada más.
¿Necesita más pruebas? Examine el código fuente de gcc:
Listado de Código 3.1: Código fuente de -O |
if (optimize >= 3)
{
flag_inline_functions = 1;
flag_unswitch_loops = 1;
flag_gcse_after_reload = 1;
/* Allow even more virtual operators. */
set_param_value ("max-aliased-vops", 1000);
set_param_value ("avg-aliased-vops", 3);
}
|
Como puede ver, cualquier valor por encima de 3 es tratado como solo
-O3.
¿Qué pasa con los parámetros redundantes?
A menudo CFLAGS y CXXFLAGS que están activadas en varios niveles de
-O están especificadas redundantemente en
/etc/portage/make.conf. Algunas veces esto ocurre por
ignorancia, pero también ocurre para permitir el filtrado o el
reemplazo de parámetros.
Muchos de los ebuilds del árbol del Portage contienen
filtrado/reemplazo de parámetros. Suele hacerse porque hay paquetes
que fallan en ciertos niveles de -O, o cuando el código fuente
es muy sensible para ser usado para cualquier parámetro adicional. El
ebuild podrá cada vez filtrar para algunas o todas las CFLAGS y
CXXFLAGS, o puede reemplazar -O con un nivel diferente.
El Manual del Desarrollador de Gentoo
resume dónde y cómo trabaja el filtrado/reemplazo de parámetros.
Es posible evitar el filtrado de -O por el listado redundante
de parámetros para un cierto nivel, como -O3, haciendo cosas
como:
Listado de Código 3.2: Especificando CFLAGS redundantes |
CFLAGS="-O3 -finline-functions -funswitch-loops"
|
Sin embargo, hacer esto no es inteligente. ¡Las CFLAGS
son filtradas por una razón! Cuando estos parámetros son filtrados es
porque es inseguro construir paquetes con ellos. Claramente, no
es seguro compilar su sistema completo con -O3 si alguno de
estos parámetros está activada para este nivel causará problemas con
ciertos paquetes. Por lo tanto, no debería intentar "saber más" que
los desarrolladores que mantienen estos paquetes. Confíe en
ellos. ¡El filtrado y reemplazo de parámetros se hace por su bien!
Si un ebuild especifica parámetros alternativos, entonces no intente
convencerlo.
No encontrará más que problemas cuando construya un paquete con
parámetros inaceptables. Cuando reporte sus problemas en Bugzilla,
los parámetros que usó en /etc/portage/make.conf serán
fácilmente visibles y se le instará a recompilar sin ellos.
¡Protéjase de los problemas de recompilar no usando parámetros
redundantes en primer lugar! No asuma automáticamente que sabe más
que los desarrolladores.
¿Qué pasa con LDFLAGS?
Los desarrolladores de Gentoo ya han configurado LDFLAGS básicas y
seguras en los perfiles base, de tal manera que no necesita cambiarlas.
¿Puedo usar parámetros por paquete?
Aviso:
El uso de parámetros por paquetes complica la depuración y el
soporte. Asegúrese de mencionarlo en los reportes de fallos si está
usando esta característica y los cambios realizados.
|
Información acerca de como utilizar las variables de entorno por
paquete (incluyendo CFLAGS) puede encontrarse en el Manual
Gentoo, "Variables de entorno por paquete".
4.
Recursos
Los siguientes recursos son un poco de ayuda en aras de comprender la
optimización:
El contenido de este documento, a no ser que se especifique
expresamente, está registrado bajo los términos de la licencia
CC-BY-SA-2.5. Se aplican las
Pautas de
Utilización del logotipo y nombre de Gentoo.
|