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 の章を読んで確認しましょう.

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


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


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


 * : から更に踏み込みます. これは特別な理由がない限り"推奨される"最適化レベルです.  は により有効になるものに加え、さらにいくつかのフラグを有効にします.  を使うと、コンパイラは、サイズが大きくなったり、たくさんの時間がかかったりしないように、コードのパフォーマンスを増加させようと試みます.


 * : これは取りうる最高の最適化レベルです. コンパイル時間とメモリ使用量を犠牲にして最適化を実施します. は性能を改善する保証がありません. 実際多くのケースで、バイナリサイズが大きくなり、メモリ使用量が増えることでシステムが遅くなります.  はいくつかのパッケージを壊すことがわかっています. これらの理由により は推奨されません.


 * : このレベルはバイナリのサイズを重視して最適化するでしょう. これは-O2フラグの中で、生成されるバイナリのサイズが増えないものを全て有効にします. CPUのキャッシュが小さかったり、ディスクストレージスペースが極端に限られている場合などに非常に有効でしょう.


 * : GCC 4.8では新しい汎用的な最適化レベル が導入されました. このレベルはコンパイル時間の短縮とデバッグエクスペリエンスを向上させる一方、妥当なランタイム性能を確保することを目的としています. 結果的に開発全体で得られるものはデフォルトの最適化レベル より向上するはずです. は を意味しないことだけ理解しておいてください.  はデバッガとやりとりするために単純に最適化をオフにするだけです.


 * : GCC 4.7では、 に加えて、 、 、 が利用できます. このオプションは規格への厳密な適合を犠牲にするため推奨されません.

前述の通り、 が推奨される最適化レベルです. もし を使用してパッケージのコンパイルが失敗する場合は、 で再コンパイルしてみましょう. うまくいかなかった場合は、 と を(エラー報告や問題の調査向けに)-O1や-O0 -g2 -ggdbのように、最適化レベルを低く設定してみてください.

-pipe
よく使うフラグに-pipeがあります. このフラグは、生成されるバイナリ自体には何の影響もありませんが、コンパイル処理が早くなります. これはコンパイルにおける各処理間で一時ファイルを使う代わりに、より多くのメモリを使うことになりますが、パイプを使うように指示します. メモリに余裕のないシステムの場合、GCCがもしかすると強制終了するかもしれません. そのような場合はこのフラグを使わないでください.

-fomit-frame-pointer
これは生成されるバイナリのサイズを減少させるために設計されているフラグで、よく利用されています. ( を除く)全ての のレベルで、このフラグを有効にしても(x86-64のように)デバッグ作業の阻害をしないアーキテクチャでは有効になります. GCCマニュアルによれば、全てのアーキテクチャにおいて、 を使えば が有効になる訳ではありません. GCC 4.6までは、もしくは を使っているときは、x86で明示的に有効にするために を使わなければなりません.

特に、Javaで書かれたアプリケーションの不具合修正をとても難しくします. もっとも、Javaだけがこのフラグの影響を受けるわけではありませんが. このように、このフラグは役立つ一方でデバッグを難しくしているのです. 特にバックトレースは役に立たなくなります. しかしながら、そんなにソフトウェアのデバッグを行う予定がなく、他に のようなデバッグ関連のCFLAGSを追加していないのであれば、 を試しに使ってみてもいいでしょう.

-msse、-msse2、-msse3、-mmmx、-m3dnow
これらのフラグはStreaming SIMD Extentions (SSE)、SSE2、SSE3、MMX、[https://ja.wikipedia.org/wiki/3DNow! 3DNow!]の命令セットをx86とx86-64アーキテクチャで有効にします. これらは主にマルチメディアやゲーム、その他の浮動小数点を多用する計算処理に有用です. その他にも有用な数学用機能の向上をいくつか含んでいます. 比較的新しいCPUならば、これらの命令セットに対応しています.

通常、正しい を使っている限り、これらのどのフラグもに加える必要はありません(例えば は を有効にします). いくつかの注意すべき例外は、比較的新しいVIAとAMD64のCPUです. VIAとAMD64はこれらの命令をサポートしますが、(SSE3のように) では有効になりません. これらのCPUについてはの出力を確認して、ふさわしいフラグを追加する必要があるでしょう.

-funroll-loopsや-fomg-optimizeを使ったら速くなったんだけど！
いいえ違います. フラグを付け加えれば付け加えるほど最適化されると言う誰かに騙されて、してやったと勘違いしているだけです. システム全体で挑戦的なフラグを使うことはあなたのアプリケーションを傷つけるでしょう. GCC マニュアルでは と を使うとバイナリは大きくなり、実行も遅くなると述べています. またいくつかの理由から、これらの二つのフラグと同時に、 や や などの似たようなフラグが、速度を最大限誇示したい人たちの間で、とても人気を博しています.

ここで本当に問題なのは、これらのフラグは危険なほどに挑戦的なフラグということです. それらのフラグが何をやらかしているのかGentoo ForumsとBugzillaあたりをよく見てください. ろくなことないですよ！

それらのフラグを や に設定し、システム全体で使う必要はありません. それらはパフォーマンスに悪影響を及ぼすだけでしょう. それらのフラグが、最先端でハイパフォーマンスなシステムを使っているかのように見せるかもしれませんが、しかしそれらは何の効果もないどころか、バイナリのサイズが膨れ上がり、無効(INVALID)や修正の必要無し(WONTFIX)と結論づけられたバグを踏むことになります.

あなたはそのような危険なフラグを使う必要はありません. '''使わないでください. ''' 、 、 という基本を守り通してください.

3より高い-Oレベルはどう？
何人かのユーザーが、 や などを使うことによってもっといいパフォーマンスを得たと誇張していますが、3より高い レベルは何の効果もありません. コンパイラは のようなCFLAGSも許容するでしょうが、それらは実質何もしないのです. の最適化を行うだけで、それ以上の最適化はしません.

さらに証拠が必要ですか？ソースコードを試してみてください.

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