コンパイル最適化ガイド
1.
はじめに
CFLAGSとCXXFLAGSとは?
CFLAGSとCXXFLAGSは、ソースコードをコンパイルするときに使われるオプションを
GNUコンパイラコレクションであるgccに伝えるために使われる環境変数です。
CFLAGSはCで書かれたソースコード、CXXFLAGSはC++で書かれたソースコード用になります。
これらはプログラムのデバッグメッセージの量を減らすのに使われたり、
エラーや警告のレベルを増加させたり、また、もちろん生成されるコードの最適化にも使われたりします。
GNU
gcc ハンドブックに利用可能なフラグとその働きの完全なリストが記載されています。
どのように使われているのでしょうか?
CFLAGSとCXXFLAGSは二通り使われ方があります。
一つ目は、プログラム毎にautomakeにより生成されたMakefileと共に使う方法です。
しかしながら、この方法はPortageツリーの中にあるパッケージをインストールする際に使うべきではありません。
二つ目は、CFLAGSとCXXFLAGSを/etc/portage/make.confで設定する方法です。
この方法を使えば、全てのパッケージはあなたが設定したフラグでコンパイルされるでしょう。
コード表示 1.1: /etc/portage/make.conf内のCFLAGS |
CFLAGS="-march=athlon64 -O2 -pipe"
CXXFLAGS="${CFLAGS}"
|
見てわかるとおり、CXXFLAGSはCFLAGSの中にある全てのフラグが設定されています。
これでやりたいことはほぼ十分できています。
CXXFLAGSの中に追加のフラグを定義する必要はありません。
よくある誤解
CFLAGSとCXXFLAGSは、
ソースコードから小さくて早いバイナリを得るにはとても効果的な方法である一方で、
ソースコード中の機能を損なったり、バイナリのサイズが膨れ上がったり、実行速度を低下させたり、
コンパイルの失敗さえも引き起こす場合もあります!
CFLAGSは特効薬ではありません。これらは自動的にあなたのシステムを早くしたり、
ディスク上のスペースが少なくなるようバイナリを縮めてはくれないでしょう。
たくさんのフラグを、システムを最適化する目的で追加することは、
確実に失敗します。払った労力に見合う実入りを得るにも限度と言うものがあります。
インターネットでは挑戦的なCFLAGSやCXXFLAGSの自慢も見受けられますが、
それらはいい影響を与えるよりも、悪影響を及ぼす可能性の方がはるかに高いです。
そもそも最適化フラグは、特定の場合に、
特定の目的の為に使われるよう設計されたと言うことを肝に銘じておきましょう。
とあるひとつのフラグが、ソースコードのごく一部を良くするからといって、
あなたのマシンにインストールされる全てをコンパイルするのに適しているという意味ではないのです!
準備はできましたか?
リスクを伴うことを理解したところで、
あなたのコンピュータのために確実で安全な最適化を見ていきましょう。
そうすれば、この先、Bugzillaで開発者に歓迎される役立つ報告をすることができます。
(開発者は、大抵、問題が再現するか確かめるために、
最小限のCFLAGSでパッケージを再コンパイルすることを要求します。
挑戦的なフラグはコードを破壊しうることを覚えておいてください。)
2.
最適化について
基本
CFLAGSとCXXGLAGSの使用目的は、
あなたのシステム向けにあつらえた、
可能な限り早くて小さな、かつ完全に動作するコードを生成することです。
時には、これらの条件は相互に排他的ですので、
うまく動作すると分かっている組み合わせをここでは使うことにします。
原則的には、お手持ちのCPUアーキテクチャ向けに利用可能な良い最適化が用意されています。
後ほど私たちは積極的なフラグについて言及するので、
あなたの探しているものも見つかるでしょう。
gccマニュアルに載っている(数えきれないほど)たくさんあるフラグについては議論せず、
基本的で最もよく知られているフラグを対象にします。
注意:
フラグが実際にどう働くか確証が得られないときはいつでもgccマニュアルの関連する章を参照してください。
もしそれでもわからないときには、Googleで検索したり、gcc
メーリングリストを調べてみてください。
|
-march
まず最初に、一番重要なフラグは-marchです。
これはお手持ちのプロセッサアーキテクチャのためのコードを生成するようコンパイラに伝えます。
つまり、特定のCPU向けのコードを生成すべきであるといっているのです。
CPUが違えば、性能が異なり、異なる命令セットをサポートし、コードの実行方法も違います。
-marchフラグは、あなたのCPUの全ての性能、特徴、命令セット、癖などに合わせて特化したコードを生成するようにコンパイラに伝えます。
たとえ/etc/portage/make.confに書いてあるCHOST変数を一般的なアーキテクチャに設定していても、
-marchを設定すれば、プログラムは指定したプロセッサ向けに最適化されるでしょう。
x86とx86-64のCPUは(とりわけ)-marchフラグを使うべきです。
どんな種類のCPUを使っていますか?以下のコマンドを実行すれば、それが分かります:
コード表示 2.1: CPU情報の取得 |
$ cat /proc/cpuinfo
|
では実際に-marchを見てみましょう。この例は古いPentium IIIチップ向けです:
コード表示 2.2: /etc/portage/make.conf: Pentium III向け |
CFLAGS="-march=pentium3"
CXXFLAGS="${CFLAGS}"
|
こちらはAMD64向けになります:
コード表示 2.3: /etc/portage/make.conf: AMD64向け |
CFLAGS="-march=athlon64"
CXXFLAGS="${CFLAGS}"
|
もしどのCPUを使っているのか分からない場合は、
-march=nativeを使うこともできるでしょう。
このフラグが設定されると、GCCはプロセッサを判別して、
自動的にふさわしいフラグを設定するでしょう。
しかしながら、
このフラグは異なるCPU向けにパッケージをコンパイルする目的では使用すべきではありません!
例えば、あるコンピュータでパッケージをコンパイルして、
しかしそれらを別のコンピュータで実行しようとしている場合
(処理の早いコンピュータで、古くて遅いマシンのためにビルドしているときなど)、
-march=nativeを使わないでください。
"native"というのはコンパイルしているマシンのCPUタイプのみに特化して、
アプリケーションのコードを生成することを意味しています。
AMD Athlon 64上で-march=nativeと共にビルドされたアプリケーションは、
古いVIA C3では実行することができないでしょう。
また、-mtuneと-mcpuフラグも利用可能です。これらのフラグは
たいてい-marchフラグが利用不可能な場合にのみ使われます。
例えば特定のプロセッサアーキテクチャは-mtuneや-mcpuが必要になるかもしれません。
残念ながら、gccの挙動はそれぞれのフラグの振る舞いが、
あるアーキテクチャから近いアーキテクチャであってもあまり一貫性はありません。
x86とx86-64のCPUにおいて、-marchは全ての利用可能な命令セットと正しいABIを使い、
そのCPUに特化したコードを生成するでしょう。
そのため古かったり種類の異なるCPUとの後方互換性は持っていません。
もし現在Gentooを動かしているシステム以外でそのコードを走らせるつもりがないならば、
-marchを使い続けてください。
i386やi486のような古いCPU向けにコードを生成する必要があるときのみ、
-mtuneの使用を考慮するべきでしょう。
-mtuneは-marchよりも一般的なコードを生成します。
特定のコードにチューニングしますが、利用可能な命令セットやABIを考慮しないのです。
-mcpuはx86やx86-64のシステム上では非推奨となっているので、使わないでください。
x86/x86-64でない(SparcやAlpha、PowerPCのような)CPUでのみ、
-marchの代わりに-mtuneや-mcpuが必要になるでしょう。
これらのアーキテクチャ上では、-mtune/-mcpuは(x86/x86-64上での)-marchと同じように振る舞うでしょう・・・しかしフラグの名前は違うのです。
繰り返しますが、gccの振る舞いとフラグ名はアーキテクチャを超えて一貫していないので、
システムでどのフラグを使うべきなのかをgccマニュアル
で必ず確認してください。
注意:
更なる-march/-mtune/-mcpuの設定についての情報は、
あなたのアーキテクチャに適したgentooインストールハンドブックの5章を読んでみてください。
また、gccマニュアルのアーキテクチャ特有のフラグのリストに、
-marchと-mcpuと-mtuneの違いについてもっと詳しい説明が書いてあります。
|
-O
次は-Oフラグについてです。これは全体の最適化レベルをコントロールします。
特にこの最適化レベルを上げることによって、ソースコードのコンパイルの時間がいくらか増えたり、
よりたくさんのメモリを使用するようになります。
-Oの設定には5つあります。
-O0、-O1、-O2、-O3、そして-Osです。
これらの中からひとつだけ選び、
/etc/portage/make.confの中で使ってみてください。
-O0を除いて、-Oの設定はいずれもいくつかの追加フラグを有効にします。
なので、どの-Oレベルで、どのフラグが有効になり、そのフラグにどんな効果があるのかを学ぶために、gccマニュアルの最適化フラグ
の章を読んで確認しましょう。
それぞれの最適化レベルについてみてみましょう:
-
-O0: このレベル("O"のあとにゼロが続いてます)は、
完全に最適化をオフにします。
CFLAGSやCXXFLAGSの中に-Oが定義されていない場合のデフォルトです。
コードが最適化されないので、普通は使われません。
-
-O1: これは最も基本的な最適化レベルです。
コンパイラはコンパイル時間をたくさんかけることなく、
高速でサイズの小さなバイナリを生成しようと試みるでしょう。
これは非常に基本的な最適化しかおこないませんが、その代わり、いつでもうまくいくはずです。
-
-O2: -O1から更に踏み込みます。
これは特別な理由がない限り推奨される最適化レベルです。
-O2は-O1により有効になるものに加え、さらにいくつかのフラグを有効にします。
-O2を使うと、コンパイラは、サイズが大きくなったり、
たくさんの時間がかかったりしないように、コードのパフォーマンスを増加させようと試みます。
-
-O3: これは最高の最適化レベルであり、また最もリスクが高いです。
このフラグを有効にしてコンパイルすると長い時間がかかるでしょうし、
それどころかこのフラグとgcc 4.xを使ってシステム全体を作るべきではありません。
gccの振る舞いが3.xバージョンからかなり変わってしまっています。
3.xでは、-O3は実行時間が-O2よりもわずかに早くなっていましたが、
これはもはやgcc 4.xでは当てはまりません。
全てのパッケージを-O3と共にコンパイルすることは、
たくさんのメモリを必要とする大きなバイナリができあがり、
さらにはコンパイルエラーや(エラーを含む)予期しないプログラムの動作をする確率が、
かなり上昇します。
このようにプラス面よりもマイナス面が勝っています。
払った労力に見合う実入りを得るにも限度がある、という原則を肝に銘じてください。
-O3を使うことはgcc 4.xでは推奨されていません。
-
-Os: このレベルはバイナリのサイズを重視して最適化するでしょう。
これは-O2フラグの中で、
生成されるバイナリのサイズが増えないものを全て有効にします。
CPUのキャッシュが小さかったり、
ディスクストレージスペースが極端に限られている場合などに非常に有効でしょう。
しかしながら、かなりの数の問題を引き起こしかねません。それが、
portageツリーの中のたくさんのebuildでこのフラグが除外されている理由でもあります。
-Osを使うことは推奨されていません。
前述の通り、-O2が推奨される最適化レベルです。
もしパッケージのコンパイルが失敗する場合は、-O3を使っていないか確認してください。
うまくいかなかった場合は、
CFLAGSとCXXFLAGSを(エラー報告や問題の調査向けに)-O1や-O0 -g2 -ggdb
のように、最適化レベルを低く設定し、パッケージを再コンパイルしてみましょう。
-pipe
よく使うフラグに-pipeがあります。
このフラグは、生成されるバイナリ自体には何の影響もありませんが、コンパイル処理が早くなります。
これはコンパイルにおける各処理間で一時ファイルを使う代わりに、
より多くのメモリを使うことになりますが、パイプを使うように指示します。
メモリに余裕のないシステムの場合、gccがもしかすると強制終了するかもしれません。
そのような場合はこのフラグを使わないでください。
-fomit-frame-pointer
これは生成されるバイナリのサイズを減少させるために設計されているフラグで、
よく利用されています。
(-O0を除く)全ての-Oのレベルで、このフラグを有効にしても(x86-64のように)デバッグ作業の阻害をしないアーキテクチャでは有効になります。
しかしながら、場合によっては明示的に有効にする必要があるかもしれません。
GNU gccマニュアルによれば、全てのアーキテクチャにおいて、
-Oを使えば有効になる訳ではありません。
特にx86においては明示的に指定する必要があります。
しかしながらx86上でこのフラグを使えば、デバッグはほとんど不可能になってしまいます。
特に、Javaで書かれたアプリケーションの不具合修正をとても難しくます。
もっとも、Javaだけがこのフラグの影響を受けるわけではありませんが。
このように、このフラグは役立つ一方でデバッグを難しくしているのです。
特にバックトレースは役に立たなくなります。
しかしながら、そんなにソフトウェアのデバッグを行う予定がなく、
他に-ggdbのようなデバッグ関連のCFLAGSを追加していないのであれば、
-fomit-frame-pointerを試しに使ってみてもいいでしょう。
重要:
-fomit-frame-pointerと似ている-momit-leaf-frame-pointerフラグを組み合わせて使わないでください。
後者のフラグを使うのはやめてください。-fomit-frame-pointerのみで十分です。
そのうえ、-momit-leaf-frame-pointerはコードの性能に悪影響を及ぼすことがわかっています。
|
-msse, -msse2, -msse3, -mmmx, -m3dnow
これらのフラグは、SSE、SSE2、SSE3、MMX、3DNow!のx86とx86-64アーキテクチャ向け命令セットを有効にします。
これらは主にマルチメディアやゲーム、その他の浮動小数点を多用する計算処理に有用です。
その他にも有用な数学用機能の向上をいくつか含んでいます。
比較的新しいCPUならば、これらの命令セットに対応しています。
重要:
CPUがこれらをサポートしているかどうかはcat /proc/cpuinfoを実行して確認してください。
その出力にサポートされているこれらの命令セットが表示されます。
pniが実際はSSE3の別名であることに注意してください。
|
通常、正しい-marchを使っている限り、
これらのどのフラグも/etc/portage/make.confに加える必要はありません
(例えば-march=noconaは-msse3を有効にします)。
いくつかの注意すべき例外は、比較的新しいVIAとAMD64のCPUです。
これらは(SSE3のような)命令をサポートしますが、-marchでは有効になりません。
これらのCPUについては、cat /proc/cpuinfoの出力を確認した後に、
ふさわしい追加フラグを有効にする必要があるでしょう。
注意:
x86とx86-64特有のフラグのリスト
を確認しましょう。
-marchで適切にCPUを指定することによって、どの命令セットが有効になるのか確認することができます。
もし命令がリストの中にあったら、改めて指定する必要はありません。
なぜならそれらは正しい-marchを使えば有効になるでしょうから。
|
3.
最適化FAQ
-funroll-loopsや-fomg-optimizeを使ったら早くなったんだけど!
いいえ違います。フラグを付け加えれば付け加えるほど最適化されると言う誰かに騙されて、
してやったと勘違いしているだけです。
システム全体で挑戦的なフラグを使うことはあなたのアプリケーションを傷つけるでしょう。
gcc マニュアルでは-funroll-loopsと-funroll-all-loopsを使うとバイナリは大きくなり、
実行も遅くなると述べています。
またいくつかの理由から、これらの二つのフラグと同時に、
-ffast-mathや-fforce-memや-fforce-addrなどの似たようなフラグが、
速度を最大限誇示したい人たちの間で、
とても人気を博しています。
ここで本当に問題なのは、これらのフラグは危険なほどに挑戦的なフラグということです。
それらのフラグが何をやらかしているのか、Gentoo Forums
とBugzillaあたりをよく見てください。
ろくなことないですよ!
それらのフラグをCFLAGSやCXXFLAGSに設定し、システム全体で使う必要はありません。
それらはパフォーマンスに悪影響を及ぼすだけでしょう。
それらのフラグが、
最先端でハイパフォーマンスなシステムを使っているかのように見せるかもしれませんが、
しかしそれらは何の効果もないどころか、バイナリのサイズが膨れ上がり、バグ報告は無効(INVALID)や修正の必要無し(WONTFIX)として処理されます。
あなたはそのような危険なフラグを使う必要はありません。使わないでください。
-march、-O、-pipeという基本を守り通してください。
3より高い-Oレベルはどう?
何人かのユーザーが、-O4や-O9などを使うことによってもっといいパフォーマンスを得たと誇張していますが、3より高い-Oレベルは何の効果もありません。
コンパイラは-O4のようなCFLAGSも許容するでしょうが、それらは実質何もしないのです。
-O3の最適化を行うだけで、それ以上の最適化はしません。
もっと証拠が必要ですか?gccのソースコード
を確認すると:
コード表示 3.1: -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);
}
|
見てのとおり、3より高いレベルであっても、結局-O3として扱われます。
冗長なフラグ指定はどう?
しばしば、/etc/portage/make.confの中で、個々の-Oレベルを指定すれば有効になるフラグを
重複してCFLAGSやCXXFLAGSに設定していることがあります。
これは時々よく知らずにやってしまうのですが、
一方で(ebuildが行う)フラグの除去や置換を回避する為に行われることがあります。
フラグの除去や置換はPortageツリーの中にある多くのebuildで行われています。
大抵は、特定の-Oレベルでパッケージをコンパイルすると失敗するため、
もしくは、フラグを追加すると、そのソースコードでは問題が出るからです。
ebuildはどちらの場合も、全部/一部のCFLAGSとCXXFLAGSを除外するか、
もしくは異なる-Oレベルに置換するでしょう。
Gentoo開発者マニュアルにフラグの除去と置換がどんな場合にどうやって使われているのか、あらましがあります。
重複してフラグを設定することによって、ある程度は-Oに対するフラグ除去を回避することができます。例えば、-O3であれば、次のようにします:
コード表示 3.2: 重複してCFLAGSを設定 |
CFLAGS="-O3 -finline-functions -funswitch-loops"
|
しかしながら、これは賢いやり方ではありません。
CFLAGSは理由があって除去されるのです!
フラグが除去されるとき、
それらのフラグでパッケージをビルドすると安全でないことを意味します。
明らかに分かっているのは、
-O3でシステム全体をコンパイルすることは安全ではないという事です。
そうすると、-O3の最適化で有効になるいくつかのフラグによって問題となるパッケージが出てくるでしょう。
そのため、それらのパッケージをメンテナンスしている開発者の"裏をかく"ことを試みるべきではありません。
開発者を信用してください。
フラグの除去と置換はあなたの利益になるから行われているのです!
もしebuildに代替のフラグが指定されているなら、それを回避しようとしないでください。
開発者が許可していないフラグでパッケージをビルドすれば、
大概は数々の問題に陥り続けることでしょう。
Bugzillaに不具合を報告する際には、
/etc/portage/make.confで使っているフラグは容易く見抜かれてしまうので、
余計なフラグを除いて再コンパイルする様に開発者に指示されるでしょう。
初めから冗長なフラグを指定しないことで、再コンパイルする手間を省いてください!
あなたが開発者よりよく知っていると根拠なく無意識に決めつけないでください。
LDFLAGSはどう?
Gentoo開発者がすでに基本的で安全なLDFLAGSを基本プロファイルにセットしているので、
それらを変更する必要はありません。
パッケージごとにフラグを変更出来るの?
警告:
パッケージごとにフラグを変更するとデバッグやサポートが込み入ってきます。
もしこの機能を使っている場合には、どんな変更をしたのか、バグレポートで言及してください。
|
パッケージごとに(CFLAGSを含む)環境変数を変更する方法は、Gentoo
ハンドブックの"パッケージごとの環境変数"で説明しています。
4.
資料
以下のいくつかの資料が更に最適化について理解する助けになるでしょう:
このドキュメントの内容は、他のものが明示されない限りは、
CC-BY-SA-2.5ライセンスです。
Gentoo Name and Logo Usage Guidelines (日本語訳)が適用されます。
|