GCC optimization/ja

このガイドは、安全で分別のあるCFLAGSとCXXFLAGSを使ったコンパイル済みコードの最適化の導入を提供します. また、一般的な最適化の裏側にある理論について述べます.

CFLAGSとCXXFLAGSとは？
CFLAGSとCXXFLAGSは、ソースコードをコンパイルするときに使われるオプションをGNUコンパイラコレクションであるgccに伝えるために使われる環境変数です. CFLAGSはCで書かれたソースコード、CXXFLAGSはC++で書かれたソースコード用になります.

これらはプログラムのデバッグメッセージの量を減らすのに使われたり、エラーや警告のレベルを増加させたり、また、もちろん生成されるコードの最適化にも使われたりします. GCC manualに利用可能なフラグとその働きの完全なリストが記載されています.

どのように使われているのでしょうか？
CFLAGSとCXXFLAGSは二通り使われ方があります. 一つ目は、プログラム毎にautomakeにより生成されたMakefileと共に使う方法です.

しかしながら、この方法はPortageツリーの中にあるパッケージをインストールする際に使うべきではありません. その代わりに、Gentooベースのシステムではの と を設定します. この方法を使えば、全てのパッケージはあなたがに設定したフラグでコンパイルされるでしょう.

見てわかるとおり、CXXFLAGSはCFLAGSの中にある全てのフラグが設定されています. 大部分のシステムはこのように設定されるべきです. 通常のユースケースでは、 だけに追加のオプションを指定するようなことはほとんどありません.

よくある誤解
CFLAGSとCXXFLAGSは、ソースコードから小さくて早いバイナリを得るにはとても効果的な方法である一方で、ソースコード中の機能を損なったり、バイナリのサイズが膨れ上がったり、実行速度を低下させたり、コンパイルの失敗さえも引き起こす場合もあります！

CFLAGSは特効薬ではありません. これらは自動的にあなたのシステムを早くしたり、ディスク上のスペースが少なくなるようバイナリを縮めてはくれないでしょう. たくさんのフラグを、システムを最適化する目的で追加することは、確実に失敗します. 払った労力に見合う実入りを得るにも限度と言うものがあります.

インターネットでは挑戦的なCFLAGSやCXXFLAGSの自慢も見受けられますが、それらはいい影響を与えるよりも、コンパイルされたバイナリに悪影響を及ぼす可能性の方がはるかに高いです. そもそもフラグは特定の場所、特定の目的のために設計されていることを忘れてはいけません. グローバルに適用することを目的としたフラグはほとんどありません.

準備はできましたか？
リスクが伴うことを理解したところで、良識的、かつ安全な最適化の方法を見ていきましょう. そうすれば、この先、Bugzillaで開発者に歓迎される役立つ報告をすることができます. （開発者は、大抵、問題が再現するか確かめるために、最小限のCFLAGSでパッケージを再コンパイルすることを要求します. 挑戦的なフラグはコードを破壊しうることを覚えておいてください!）

基本
CFLAGSとCXXGLAGSの使用目的は、あなたのシステム向けにあつらえた、可能な限り早くて小さな、かつ完全に動作するコードを生成することです. 時には、これらの条件は相互に排他的ですので、うまく動作すると分かっている組み合わせをここでは使うことにします. 原則的には、お手持ちのCPUアーキテクチャ向けに利用可能な良い最適化が用意されています. 参考情報として、さらにアグレッシブなフラグを後述します. このガイドではGCCのマニュアルに記載されているすべてのオプションを議論する訳ではありません. 基本的な、そしてもっとも標準的なオプションについて解説します.

-march
最初の、そしてもっとも重要なオプションは です. このオプションはコンパイラに対してどのシステムプロセッサアーキテクチャ (もしくは"arch")のためのコードを生成するのかを指示します. つまり、特定のCPU向けのコードを生成すべきであるといっているのです. CPUが違えば、性能が異なり、異なる命令セットをサポートし、コードの実行方法も違います. フラグは、あなたのCPUの全ての性能、特徴、命令セット、癖などに合わせて特化したコードを生成するようにコンパイラに伝えます.

たとえに書いてある 変数を一般的なアーキテクチャに設定していても、 を設定すれば、プログラムは指定したプロセッサ向けに最適化されるでしょう. x86とx86-64のCPUは(とりわけ) フラグを使うべきです.

どんな種類のCPUを使っていますか？以下のコマンドを実行すれば、それが分かります.

さらに と の値を含む詳細な情報を得たい場合は、以下を実行してください.

では実際に を見てみましょう. この例は古いPentium IIIチップ向けです.

こちらはAMD64向けです.

CPUのタイプが決められない場合、またはどの設定を使えばいいかわからない場合、 を使うことができます. このフラグを使った場合、GCCはプロセッサを判別して、自動的にふさわしいフラグを設定するでしょう. しかしながら、このフラグは異なるCPU向けにパッケージをコンパイルする目的では使用すべきではありません！

例えば、あるコンピュータでパッケージをコンパイルして、しかしそれらを別のコンピュータで実行しようとしている場合 (処理の早いコンピュータで、古くて遅いマシンのためにビルドしているときなど)、 を使わないでください. "native"というのはコンパイルしているマシンのCPUタイプのみに特化して、アプリケーションのコードを生成することを意味しています. AMD Athlon 64上で と共にビルドされたアプリケーションは、古いVIA C3では実行することができないでしょう.

また、 と フラグも利用可能です. これらのフラグはたいてい フラグが利用できない場合にのみ使われます. 例えば特定のプロセッサアーキテクチャは や が必要になるかもしれません. 残念ながら、GCCの挙動はそれぞれのフラグの振る舞いが、あるアーキテクチャから近いアーキテクチャであってもあまり一貫性はありません.

x86とx86-64のCPUにおいて、 は利用可能な命令セットと正しいABIを使い、そのCPUに特化したコードを生成するでしょう. そのため古かったり種類の異なるCPUとの後方互換性は持っていません. i386やi486のような古いCPU向けにコードを生成する必要があるときのみ、 の使用を考慮するべきでしょう. は よりも一般的なコードを生成します. 特定のCPUコードにチューニングしますが、利用可能な命令セットやABIを考慮しないのです. はx86やx86-64のシステム上では非推奨となっているので、使わないでください.

x86/x86-64でない(SparcやAlpha、PowerPCのような)CPUでのみ、 の代わりに や が必要になるでしょう. これらのアーキテクチャ上では、 / は(x86/x86-64上での) と同じように振る舞うでしょう･･･しかしフラグの名前は違うのです. 繰り返しますが、GCCの振る舞いとフラグ名はアーキテクチャを超えて一貫していないので、システムでどのフラグを使うべきなのかをGCCのマニュアルで必ず確認してください.

-O
次は フラグについてです. これは全体の最適化レベルをコントロールしますが、特にこの最適化レベルを上げることによって、ソースコードのコンパイルの時間がいくらか増えたり、よりたくさんのメモリを使用するようになります.

７つの 設定があります. 、 、 、 、 、 、 です. で指定できるのはこのうちの一つだけです.

を除いて、 の設定はいずれもいくつかの追加フラグを有効にします. なので、どの レベルで、どのフラグが有効になり、そのフラグにどんな効果があるのかを学ぶために、GCCマニュアルのoptimization options の章を読んで確認しましょう.

私たちはそれぞれの最適化レベルを調べてみましょう:

や の中に が定義されていない場合のデフォルトです. このレベルはコンパイル時間を短縮して、生成するデバッグ情報を改善しますが、いくつかのアプリケーションは最適化がないと正しく動作しません. よって、デバッグ以外では推奨されません.

コンパイラはコンパイル時間をたくさんかけることなく、高速でサイズの小さなバイナリを生成しようと試みるでしょう. これは基本的な最適化しかおこないませんが、その代わり、いつでもうまくいくはずです.


 * : A step up from . The recommended level of optimization unless the system has 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.


 * : 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. Using   is not recommended.


 * : optimizes code for size. It activates all  options that do not increase the size of the generated code. It can be useful for machines that have extremely limited disk storage space and/or CPUs with small cache sizes.


 * : In GCC 4.8, a new general optimization level,, 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  .  Note that   does not imply  , it simply disables optimizations that may interfere with debugging.


 * : New in GCC 4.7, consists of  plus ,  , and  . 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 while not using, try rebuilding with that option. As a fallback option, try setting the  and   to a lower optimization level, such as   or even   (for error reporting and checking for possible problems).

-pipe
A common flag is. This flag 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 those cases 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 it may need to activated. In that case add it to the flags. Though the GCC manual does not specify all architectures, it is turned on by using the  option. Use the  option to explicitly activate it on x86, with GCC up to version 4.6 or when using. However, using this flag will make debugging hard or 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. When not doing software debugging and no other debugging-related CFLAGS such as  have been used, then try using.

-msse, -msse2, -msse3, -mmmx, -m3dnow
These flags enable the Streaming SIMD Extentions (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.

Normally none of these flags need to be added to, as long as the system is 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 additional flags will need to be enabled where appropriate after checking.

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 applications when used system-wide. Even the GCC manual says that using  and   will make 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 do not need to use those flags globally in  or. 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 the 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:

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

What about compiling outside the target machine?
Some readers might wonder if compiling outside the target machine with a strictly inferior CPU or GCC sub-architecture will result in inferior optimization results (compared to a native compilation). The answer is simple: No. Regardless of the actual hardware on which the compilation takes place and the CHOST for which GCC was built, as long as the same arguments are used (except for ) and the same version of GCC is used (although minor version might be different), the resulting optimizations are strictly the same.

To exemplify, if Gentoo is installed on a machine whose GCC's CHOST is i686-pc-linux-gnu, and a Distcc server is setup on another computer whose GCC's CHOST is i486-linux-gnu, then there is no need to be afraid that the results would be less optimal because of the strictly inferior sub-architecture of the remote compiler and/or hardware. The result would be as optimized as a native build, as long as the same options are passed to both compilers (and the  parameter doesn't get a   argument). In this particular case the target architecture needs to be specified explicitly as explained in Distcc and -march=native.

The only difference in behavior between two GCC versions built targeting different sub-architectures is the implicit default argument for the  parameter, which is derived from the GCC's CHOST when not explicitly provided in the command line.

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:

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 they do not need to be changed.

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:


 * GCC online documentation


 * Gentoo インストールハンドブックの第5章


 * man make.conf


 * Wikipedia


 * Gentoo Forums