GCC optimization/it

Questa guida costituisce un'introduzione all'ottimizzazione del codice compilato utilizzando valori sicuri per le variabili CFLAGS e CXXFLAGS. Inoltre viene descritta la teoria a fondamento dell'ottimizzazione in generale.

Cosa sono CFLAGS e CXXFLAGS?
CFLAGS e CXXFLAGS sono variabili d'ambiente che vengono utilizzate per comunicare alla GNU Compiler Collection quali sono le opzioni da attivare quando viene compilato del codice sorgente. Le CFLAGS si riferiscono al codice sorgente scritto in C mentre le CXXFLAGS si riferiscono al codice sorgente scritto in C++.

Possono essere usate per ridurre la quantità di messaggi per il debug, per incrementare i livelli dei messaggi di avvertimento e ovviamente per ottimizzare il codice prodotto. Il manuale GCC contiene una lista completa delle opzioni disponibili, e per ciascuna di esse viene fornita una spiegazione del motivo per cui vengono usate.

Come vengono usate?
Le CFLAGS e le CXXFLAGS possono essere utilizzate in due modi. Il primo modo consiste nell'utilizarle a livello di programmi singoli con i Makefile generati da automake.

Tuttavia ciò non dovrebbe essere fatto quando si installano pacchetti di Portage. È preferibile invece impostare le CFLAGS e le CXXFLAGS nel file. In questo modo tutti i pacchetti vengono compilati usando le stesse impostazioni.

CFLAGS in /etc/portage/make.conf

Come è possibile vedere le CXXFLAGS sono state impostate con le stesse opzioni presenti nelle CFLAGS. Si tratta della configurazione più sicura, e in genere non dovrebbe mai sorgere la necessità di specificare opzioni diverse per le CXXFLAGS.

Convinzioni errate
Anche se le CFLAGS e le CXXFLAGS possono essere un modo molto valido per ottenere codice binario più piccolo o più veloce esse, se utilizzate in modo errato, possono anche compromettere la funzionalità del codice stesso, aumentare a dismisura le sue dimensioni, ridurre drasticamente le prestazioni o anche provocare errori di compilazione!

Le CFLAGS non sono una panacea ad ogni male: non rendono sempre il sistema dell'utente più veloce e non riducono sempre le dimensioni del codice binario. Aggiungere sempre più flag nell'intento di ottimizzare sempre più il sistema conduce sicuramente al fallimento. C'è un punto oltre al quale si ottengono miglioramenti ogni volta più scarsi.

A dispetto di certe informazioni che è possibile trovare su Internet, CFLAGS e CXXFLAGS aggressive fanno solitamente più male che bene alla maggior parte dei programmi. Occorre tenere presente che molte opzioni sono state progettate per essere usate in situazioni particolari e per ragioni specifiche. Il fatto che una certa opzione sia positiva per una determinata porzione di codice non significa che tale opzione sia per questo adatta ad essere utilizzata globalmente per compilare tutti i programmi del sistema!

Pronto?
Adesso che il lettore è stato informato dei possibili rischi è possibile esaminare alcune ottimizzazioni sicure, utilizzando le quali si ha l'approvazione degli sviluppatori quando si segnala un problema su Bugzilla. Gli sviluppatori di solito richiedono all'utente di ricompilare il pacchetto problematico con un insieme ridotto di CFLAGS per vedere se il problema persiste. Si ricordi che opzioni aggressive possono rovinare il codice.

Le basi
Lo scopo che ci si pone utilizzando le CFLAGS e le CXXFLAGS è di creare codice su misura per il sistema dell'utente; tale sistema dovrebbe funzionare perfettamente ed essere anche snello e veloce, se possibile. Alcune volte queste ultime due condizioni si escludono a vicenda, e pertanto ci si limiterà ad utilizzare una combinazione nota per funzionare molto bene. Idealmente si tratta della migliore combinazione per ogni architettura di CPU. Verrà fatta menzione delle opzioni aggressive più tardi in modo tale che l'utente possa sapere cosa bisogna evitare. Non verrà discussa ogni opzione presente nel manuale di  (ce ne sono centinaia) ma verranno spiegate solo le opzioni fondamentali e quelle più comuni.

-march
La più importante opzione è. Questa comunica al compilatore di produrre codice per una certa architettura di processori. CPU diverse possiedono diverse funzionalità, supportano diversi insiemi di istruzioni e hanno diverse modalità di eseguire il codice. L'opzione  indica al compilatore di produrre codice specifico per una data CPU, considerando tutte le sue funzionalità, gli insiemi di istruzioni e le modalità di eseguire il codice.

Anche se la variabile CHOST nel file specifica già l'architettura generica da utilizzare,   dovrebbe comunque essere utilizzata anch'essa in modo da ottimizzare il codice per uno specifico processore. In particolare sono le CPU x86 e x86-64 che dovrebbero utilizzare l'opzione.

Quale CPU si possiede? Per scoprirlo, si esegua il seguente comando:

Questo è un esempio per un vecchio Pentium III:

/etc/portage/make.conf: Pentium III

Questo è invece un esempio per una CPU AMD a 64-bit:

/etc/portage/make.conf: AMD64

Se non si è sicuri di sapere quale CPU si possiede ci si può semplicemente limitare ad utilizzare l'opzione. Con questa opzione GCC determina automaticamente di quale processore si dispone e imposta di conseguenza le opzioni più appropriate. Questa opzione, tuttavia, non dovrebbe essere utilizzata qualora si intenda compilare pacchetti per una CPU differente!

Se il lettore sta compilando pacchetti su un certo computer ma intende eseguirli su un computer diverso (ad esempio nel caso in cui si dispone di un computer veloce che compila pacchetti da utilizzare su un'altro computer più lento) non bisogna utilizzare l'opzione. Native significa che il codice prodotto da una certa CPU potrà essere eseguito solo su quel tipo di CPU. Le applicazioni compilate con  su una CPU AMD Athlon 64 non potranno essere eseguite su una vecchia CPU VIA C3.

Esistono anche le opzioni  e. Esse sono normalmente utilizzate quando l'opzione  non è disponibile; i processori di alcune architetture possono richiedere   o addirittura. Sfortunatamente il comportamento di  non è molto consistente se ci si sposta da un'architettura ad un'altra.

Per CPU x86 o x86-64  genera codice specifico per una data CPU usando gli insiemi di istruzioni disponibili e la corretta ABI; non viene garantita la compatibilità con CPU diverse o con CPU più vecchie. Se non si necessita di eseguire codice su un sistema diverso da quello sul quale è presente Gentoo si può continuare ad utilizzare. Il lettore dovrebbe considerare l'utilizzo di  solamente se abbia la necessità di generare codice per CPU quali i386 o i486. produce codice più generico che nel caso di ; anche se ottimizza il codice per una certa CPU non prende in considerazione gli insiemi di istruzioni o l'ABI disponibile. Non utilizzare  su sistemi x86 o x86-64 in quanto è obsoleta su queste architetture.

Solo CPU diverse da x86/x86-64 (come Sparc, Alpha e PowerPC) possono richiedere  o   invece che. Su queste architetture  e   si comportano a volte allo stesso modo di   su architettura x86/x86-64, e cambia solo il nome. Si ricordi ancora una volta che il comportamento di  non è consistente su tutte le architetture, e che pertanto occorre controllare il manuale per determinare quali opzioni bisogna utilizzare sul proprio sistema.

-O
La prossima variabile da considerare è. Essa permette di controllare il livello complessivo di ottimizzazione. Con  la compilazione del codice richiede un po' più di tempo e potrebbe richiedere molta più memoria, specialmente se si innalza il livello di ottimizzazione.

Esistono sette possibili impostazioni per : ,  ,  ,  ,  ,   e. Occorre scegliere solo una tra le precendenti impostazioni e applicarla nel file.

Con la sola eccezione di, ciascuna delle impostazioni   attiva alcune opzioni aggiuntive. Assicurarsi quindi di leggere il capitolo del manuale GCC sulle opzioni per l'ottimizzazione per capire quali opzioni vengono attivate per ciascuno dei livelli  e qual'è la loro funzione.

Let's examine each optimization level:


 * : This level (that's the letter "O" followed by a zero) turns off optimization entirely and is the default if no  level is specified in CFLAGS or CXXFLAGS.  This reduces compilation time and can improve debugging info, but some applications will not work properly without optimization enabled.  This option is not recommended except for debugging purposes.


 * : This is the most basic optimization level. The compiler will try to produce faster, smaller code without taking much compilation time. It's pretty basic, but it should get the job done all the time.


 * : A step up from  . This is the recommended level of optimization unless you have special needs.   will activate a few more flags in addition to the ones activated by   . With  , the compiler will attempt to increase code performance without compromising on size, and without taking too much compilation time.


 * : This is the highest level of optimization possible. It enables optimizations that are expensive in terms of compile time and memory usage.  Compiling with   is not a guaranteed way to improve performance, and in fact in many cases can slow down a system due to larger binaries and increased memory usage.    is also known to break several packages.  Therefore, using   is not recommended.


 * : This option will optimize your code for size. It activates all  options that don't increase the size of the generated code. It can be useful for machines that have extremely limited disk storage space and/or have CPUs with small cache sizes.


 * : In GCC 4.8, a new general optimization level, -Og, has been introduced. It addresses the need for fast compilation and a superior debugging experience while providing a reasonable level of runtime performance. Overall experience for development should be better than the default optimization level -O0. Note that -Og does not imply -g, it simply disables optimizations that may interfere with debugging.


 * : New in GCC 4.7, consists of -O3 plus -ffast math, -fno-protect-parens, and -fstack-arrays. This option breaks strict standards compliance, and is not recommended for use.

As previously mentioned,  is the recommended optimization level. If package compilation fails and you aren't using, try rebuilding with that option. As a fallback option, try setting your CFLAGS and CXXFLAGS to a lower optimization level, such as  or even   (for error reporting and checking for possible problems).

-pipe
A common flag is. This flag actually has no effect on the generated code, but it makes the compilation process faster. It tells the compiler to use pipes instead of temporary files during the different stages of compilation, which uses more memory. On systems with low memory, GCC might get killed. In that case, do not use this flag.

-fomit-frame-pointer
This is a very common flag designed to reduce generated code size. It is turned on at all levels of  (except  ) on architectures where doing so does not interfere with debugging (such as x86-64), but you may need to activate it yourself by adding it to your flags. Though the  manual does not specify all architectures it is turned on by using , you will need to explicitly activate it on x86. However, using this flag will make debugging hard to impossible.

In particular, it makes troubleshooting applications written in Java much harder, though Java is not the only code affected by using this flag. So while the flag can help, it also makes debugging harder; backtraces in particular will be useless. However, if you don't plan to do much software debugging and haven't added any other debugging-related CFLAGS such as, then you can try using.

-msse, -msse2, -msse3, -mmmx, -m3dnow
These flags enable the SSE, SSE2, SSE3, MMX, and 3DNow! instruction sets for x86 and x86-64 architectures. These are useful primarily in multimedia, gaming, and other floating point-intensive computing tasks, though they also contain several other mathematical enhancements. These instruction sets are found in more modern CPUs.

You normally don't need to add any of these flags to as long as you are using the correct   (for example,   implies  ). Some notable exceptions are newer VIA and AMD64 CPUs that support instructions not implied by  (such as SSE3). For CPUs like these you'll need to enable additional flags where appropriate after checking the output of.

But I get better performance with -funroll-loops -fomg-optimize!
No, you only think you do because someone has convinced you that more flags are better. Aggressive flags will only hurt your applications when used system-wide. Even the  manual says that using   and   makes code larger and run more slowly. Yet for some reason, these two flags, along with,  ,  , and similar flags, continue to be very popular among ricers who want the biggest bragging rights.

The truth of the matter is that they are dangerously aggressive flags. Take a good look around the Gentoo Forums and Bugzilla to see what those flags do: nothing good!

You don't need to use those flags globally in CFLAGS or CXXFLAGS. They will only hurt performance. They may make you sound like you have a high-performance system running on the bleeding edge, but they don't do anything but bloat your code and get your bugs marked INVALID or WONTFIX.

You don't need dangerous flags like these. Don't use them. Stick to the basics:,  , and.

What about -O levels higher than 3?
Some users boast about even better performance obtained by using,  , and so on, but the reality is that   levels higher than 3 have no effect. The compiler may accept CFLAGS like, but it actually doesn't do anything with them. It only performs the optimizations for, nothing more.

Need more proof? Examine the  source code:

-O source code

As you can see, any value higher than 3 is treated as just.

What about redundant flags?
Oftentimes CFLAGS and CXXFLAGS that are turned on at various  levels are specified redundantly in. Sometimes this is done out of ignorance, but it is also done to avoid flag filtering or flag replacing.

Flag filtering/replacing is done in many of the ebuilds in the Portage tree. It is usually done because packages fail to compile at certain  levels, or when the source code is too sensitive for any additional flags to be used. The ebuild will either filter out some or all CFLAGS and CXXFLAGS, or it may replace  with a different level.

The Gentoo Developer Manual outlines where and how flag filtering/replacing works.

It's possible to circumvent  filtering by redundantly listing the flags for a certain level, such as , by doing things like:

Specifying redundant CFLAGS

However, this is not a smart thing to do. CFLAGS are filtered for a reason! When flags are filtered, it means that it is unsafe to build a package with those flags. Clearly, it is not safe to compile your whole system with  if some of the flags turned on by that level will cause problems with certain packages. Therefore, you shouldn't try to "outsmart" the developers who maintain those packages. Trust the developers. Flag filtering and replacing is done for your benefit! If an ebuild specifies alternative flags, then don't try to get around it.

You will most likely continue to run into problems when you build a package with unacceptable flags. When you report your troubles on Bugzilla, the flags you use in will be readily visible and you will be told to recompile without those flags. Save yourself the trouble of recompiling by not using redundant flags in the first place! Don't just automatically assume that you know better than the developers.

What about LDFLAGS?
The Gentoo developers have already set basic, safe LDFLAGS in the base profiles, so you don't need to change them.

Can I use per-package flags?
Information on how to use per-package environment variables (including CFLAGS) is described in the Gentoo Handbook, "Per-Package Environment Variables".

Resources
The following resources are of some help in further understanding optimization:


 * The GCC online documentation


 * Chapter 5 of the Gentoo Installation Handbooks




 * Wikipedia


 * The Gentoo Forums

Acknowledgements
We would like to thank the following authors and editors for their contributions to this guide:


 * nightmorph