GCCの最適化
このガイドは、安全で分別のある CFLAGS と CXXFLAGS を使って、コンパイルされるコードを最適化する手法を紹介します。また、最適化の背景にある一般的な理論について述べます。
Gentoo システムのデフォルトの CFLAGS は make.conf で設定することができます。CFLAGS はパッケージ単位で指定することもできます。
はじめに
CFLAGS と CXXFLAGS とは?
CFLAGSとCXXFLAGSは、CやC++のソースコードをコンパイルするときに使われるオプションを、コンパイラに指示するために慣例的に使われる環境変数の一種です。これらの環境変数は標準化されているわけではありませんがそれに近い状況で用いられており、コンパイラを使う際に追加のオプションを指定したいなら理解しておく必要があります。GNU makeでは一般的によく使われている環境変数のリストが記されています。
Gentooシステムでは大多数のパッケージがCまたはC++で書かれているため、これらの環境変数はシステムがどのように構成されるかに多大な影響を及ぼします。そのため管理者にはこれらを正しく設定することが求められます。
これらはプログラムのデバッグメッセージの量を減らすために使われたり、エラーや警告のレベルを増加させたり、また、もちろん生成されるコードの最適化にも使われます。GCCのマニュアルに利用可能なフラグとその効果の完全なリストが記載されています。
どのように使われているのでしょうか?
普通CFLAGSとCXXFLAGSは、configureスクリプトを走らせるか、プログラム毎にautomakeにより生成されたMakefileによってセットされます。Gentooシステムでは、CFLAGSとCXXFLAGSは/etc/portage/make.confで設定します。このファイルに定義された変数はportageが呼び出したプログラムに適用され、その設定に基づいてプログラムがコンパイルされます。
CFLAGS="-march=skylake -O2 -pipe"
CXXFLAGS="${CFLAGS}"
USEフラグは複数の行で構成できますが、CFLAGSを複数の行で構成するとcmakeのようなプログラムで問題が発生します。問題を避けるために、CFLAGSの宣言は極力スペースで区切られた単一行で構成するようにしてください。bug #500034が参考になります。
見てわかるとおり、CXXFLAGSはCFLAGSの中にある全てのフラグが設定されています。大部分のシステムはこのように設定されるべきです。通常のケースでは、CXXFLAGSだけに追加のオプションを指定するようなことはほとんどありませんし、大体そういったオプションはグローバルに適用すべきではありません。
Safe CFLAGS の記事は、初心者がシステムの最適化を始めるのに役立つかもしれません。
よくある誤解
CFLAGSでコンパイラの最適化をするのはより小さく高速なバイナリを生成するには効果的な方法ですが、一方で正しく動作しない、サイズの巨大化や動作速度の低下を引き起こしたり、そもそもコンパイルできないなどの問題を起こす可能性があります。CFLAGSを弄っているとこうしたことはあっさりと発生します。適当に設定してはいけません。
/etc/portage/make.confにて設定したCFLAGSはシステム上の全てのパッケージに適用されます。そのため管理者は至極一般的で広く適用しても問題ないオプションのみを設定するのが普通です。これに加えて各々のパッケージがebuildやコンパイル中にさらに必要なオプションを自動で追加しています。
準備はできましたか?
リスクを伴うことを理解したところで、良識的、かつ安全な最適化の方法を見ていきましょう。そうすれば、この先 Bugzilla で開発者に歓迎され、役立つ報告をすることができます。(開発者は、大抵、問題が再現するか確かめるために、最小限の CFLAGS でパッケージを再コンパイルすることを要求します。挑戦的なフラグはコードを破壊しうることを覚えておいてください!)
最適化について
基本
CFLAGSとCXXGLAGSの使用目的は、システム向けにあつらえた、可能な限り早くて小さな、かつ完全に動作するコードを生成することです。時には、これらの条件は相互に排他的ですので、うまく動作すると分かっている組み合わせをここでは扱うことにします。原則、それぞれのCPUアーキテクチャ向けに利用可能なよい最適化が用意されています。参考情報として、さらにアグレッシブなフラグを後述します。このガイドではGCCのマニュアルに記載されているすべてのオプションを紹介する訳ではありません。基本的な、そして最も標準的なオプションについてのみ解説します。
フラグがどういう働きをするのか不明なときは、GCC manualの関連する章を参照してください。もしそれでもわからない場合は、ウェブ検索をするか、GCC mailing listsをチェックしましょう
-march
最初の、そしてもっとも重要なオプションは -march
です。このオプションはコンパイラに対してどのシステムプロセッサアーキテクチャ (arch とも) のためのコードを生成するのかを指示します。つまり、特定の CPU 向けのコードを生成すべきであるといっているのです。CPU が違えば、性能が異なり、異なる命令セットをサポートし、コードの実行方法も違います。-march
フラグは、あなたの CPU の全ての性能、機能、命令セット、癖などに合わせて特化したコードを生成するようにコンパイラに伝えます。例えば AVX 命令の恩恵を受けたいなら、ソースコードがそれに対応しておく必要があります。
-march=
は ISA (訳註: 命令セットアーキテクチャ) 指定のオプションで、コンパイラにどんな命令が利用可能かを伝えます。Intel/AMD64 プラットフォームで -march=native -O2
を使用すると、AVX 命令はサポートされますが SSE の XMM レジスタがサポートされない場合があります。AVX の YMM レジスタを最大限利用したい場合、-ftree-vectorize
-O3
または -Ofast
も指定する必要があります。[1]
-ftree-vectorize
は最適化オプション (-O3
と -Ofast
においてはデフォルト) で、可能であれば選択された ISA を使用してループのベクトル化を試みます。かつて -O2
で有効化されていなかった理由は、常にコードを改善するわけではなく、またコードを遅くしてしまう可能性があり、そしてたいていはコードを大きくしてしまうためです; 実際、これはループなどの状況次第です。GCC 12 以降、このオプションはデフォルトで、コードサイズと速度メリットをうまく両立させるために低コストモデル (-fvect-cost-model=very-cheap
) を使って有効化されています。コストモデルは -fvect-cost-model
によって指定することができます。
たとえ/etc/portage/make.confに書いてあるCHOST変数を一般的なアーキテクチャに設定していても、-march
を設定すれば、プログラムは指定したプロセッサ向けに最適化されるでしょう。x86とx86-64のCPUは(とりわけ)-march
フラグを使うべきです。
どんな種類のCPUを使っていますか?以下のコマンドを実行すれば、それが分かります。
user $
cat /proc/cpuinfo
あるいは、app-portage/cpuid2cpuflags をインストールし、ツールが CPU_FLAGS_* などの変数を通じて表示した利用可能な CPU 特有のオプションを /etc/portage/package.use/00cpuflags ファイルに追加することもできます:
user $
cpuid2cpuflags
CPU_FLAGS_X86: aes avx avx2 f16c fma3 mmx mmxext pclmul popcnt sha sse sse2 sse3 sse4_1 sse4_2 sse4a ssse3
root #
echo "*/* $(cpuid2cpuflags)" > /etc/portage/package.use/00cpu-flags
さらにmarch
とmtune
の値を含む詳細な情報を得たい場合は、以下の2つのコマンドが使えます。
- 最初のコマンドはコンパイラにリンクを行わないようにさせ(
-c
)、また、--help
をコマンドラインオプションを明らかにせよとではなく、あるオプションが有効か無効かを示せという意味に解釈させます(-Q
)。ここでは、選択されたターゲットで有効なオプションが出力されます:user $
gcc -c -Q -march=native --help=target
- 二つ目のコマンドはヘッダーファイルをビルドしますが、実際には処理は行わずそれらを画面に表示する(
-###
)ことでコンパイラーディレクティブを表示します。出力の最終行がすべての最適化オプションと選択されたアーキテクチャーを含むコマンドです:user $
gcc -### -march=native /usr/include/stdlib.h
- (=sys-devel/gcc-11 では) より一般的なプロセッサアーキテクチャで
-march
を定義するために、glibc-hwcaps 機能 (>=sys-libs/glibc-2.33) を利用することができます:
user $
/lib64/ld-linux-x86-64.so.2 --help
... Subdirectories of glibc-hwcaps directories, in priority order: x86-64-v4 x86-64-v3 (supported, searched) x86-64-v2 (supported, searched) x86_64 (supported, searched)
この例では、CPU は x86-64-v3 psABI x86_64 をサポートしており、この情報を利用して -march=x86-64-v3
とすることができます。
では実際に -march
を見てみましょう。この例は古い AMD Athlon 64 チップ向けです。
CFLAGS="-march=athlon64"
CXXFLAGS="${CFLAGS}"
こちらはよくある Intel プロセッサ向けです。
CFLAGS="-march=skylake"
CXXFLAGS="${CFLAGS}"
CPUのタイプが決められない場合、またはどの設定を使えばいいかわからない場合、-march=native
を使うことができます。このフラグを使った場合、GCCはプロセッサを判別して、自動的にふさわしいフラグを設定するでしょう。しかしながら、このフラグは異なるCPU向けにパッケージをコンパイルする目的では使用すべきではありません!
例えば、あるコンピュータでパッケージをコンパイルして、しかしそれらを別のコンピュータで実行しようとしている場合 (処理の早いコンピュータで、古くて遅いマシンのためにビルドしているときなど)、-march=native
を使わないでください。"native"というのはコンパイルしているマシンのCPUタイプのみに特化して、アプリケーションのコードを生成することを意味しています。Intel Core 上で-march=native
と共にビルドされたアプリケーションは、古い Intel Atom では実行することができないでしょう。
また、-mtune
と-mcpu
フラグも利用可能です。これらのフラグはたいてい-march
フラグが利用できない場合にのみ使われます。例えば特定のプロセッサアーキテクチャは-mtune
や-mcpu
が必要になるかもしれません。残念ながら、GCCの挙動はそれぞれのフラグの振る舞いが、あるアーキテクチャから近いアーキテクチャであってもあまり一貫性はありません。
x86とx86-64のCPUにおいて、-march
は利用可能な命令セットと正しいABIを使い、そのCPUに特化したコードを生成するでしょう。そのため古かったり種類の異なるCPUとの後方互換性は持っていません。i386やi486のような古いCPU向けにコードを生成する必要があるときのみ、-mtune
の使用を考慮するべきでしょう。-mtune
は-march
よりも一般的なコードを生成します。特定のCPUコードにチューニングしますが、利用可能な命令セットやABIを考慮しないのです。-mcpu
はx86やx86-64のシステム上では非推奨となっているので、使わないでください。
x86/x86-64でない(ARM、SPARC、Alpha、PowerPCのような)CPUでのみ、-march
の代わりに-mtune
や-mcpu
が必要になるでしょう。これらのアーキテクチャ上では、-mtune
/-mcpu
は(x86/x86-64上での)-march
と同じように振る舞うでしょう・・・しかしフラグの名前は違うのです。繰り返しますが、GCCの振る舞いとフラグ名はアーキテクチャを超えて一貫していないので、システムでどのフラグを使うべきなのかをGCCのマニュアルで必ず確認してください。
更なる
-march
/-mtune
/-mcpu
の設定についての情報は、あなたのアーキテクチャに適した Gentoo インストールハンドブックの5章を読んでみてください。また、GCCマニュアルのarchitecture-specific optionsのリストに、-march
と-mcpu
と-mtune
の違いについてもっと詳しい説明があります。-O
-O3
や -Ofast
を使用すると、一部のパッケージはコンパイル中に壊れたり、実行時に誤動作する原因になることがあります。特定のCFLAGS/CXXFLAGSでビルドされたすべてのパッケージを表示したい場合、以下のコマンドが使えます:
grep Ofast /var/db/pkg/*/*/CFLAGS
次は-O
フラグについてです。これは全体の最適化レベルをコントロールしますが、特にこの最適化レベルを上げることによって、ソースコードのコンパイルの時間がいくらか増えたり、よりたくさんのメモリを使用するようになります。
8 つの -O
設定があります。-O0
、-O1
、-O2
、-O3
、-Os
、-Oz
、-Og
、-Ofast
です。/etc/portage/make.conf で指定できるのはこのうちの一つだけです。
-O0
を除いて、-O
の設定はいずれもいくつかの追加フラグを有効にします。なので、どの-O
レベルで、どのフラグが有効になり、そのフラグにどんな効果があるのかを学ぶために、GCCマニュアルのoptimization options
の章を読んで確認しましょう。
では、それぞれの最適化レベルを見てみましょう。
-O0
: このレベル("O"のあとにゼロが続いてます)は、完全に最適化をオフにします。CFLAGSやCXXFLAGSの中に-O
が定義されていない場合のデフォルトです。このレベルはコンパイル時間を短縮して、生成するデバッグ情報を改善しますが、いくつかのアプリケーションは最適化がないと正しく動作しません。よって、デバッグ以外では推奨されません。
-O1
: これは最も基本的な最適化レベルです。コンパイラは時間をたくさんかけることなく、高速でサイズの小さなバイナリを生成しようと試みるでしょう。これは基本的な最適化しかおこないませんが、その代わり、いつでもうまくいくはずです。
-O2
:-O1
から更に踏み込みます。これは特別な理由がない限り"推奨される"最適化レベルです。-O2
は-O1
で有効になるものに加え、さらにいくつかのフラグを有効にします。-O2
を使うと、コンパイラはサイズとコンパイル時間に妥協せずにコードのパフォーマンスを改善しようと試みます。このレベルではSSEやAVXは利用されますが、YMMレジスタは-ftree-vectorize
も有効にしない限り使われません。
-O3
:-O2
の有効化に加えて、コンパイル時間とメモリ使用量を犠牲にした最適化を実施します。-O3
は性能を改善することが多いですが、その保証はありません。歴史的に-O3
はコンパイラのバグを明るみに出すことがありましたが、近頃は-O3
に伴う問題はほぼ必ず未定義動作 (UB) の実例であり、つまりコードに問題があり修正されるべきものです。一部のパッケージは今でも-O3
で壊れることがわかっています。パッケージのテストスイートを実行させる場合を除き、-O3
は推奨されません。一方、このレベルはコード内のループをベクトル化する-ftree-vectorize
を有効化し、AVX YMM レジスタも利用されます。
-Ofast
: GCC 4.7 では、-O3
に加えて-ffast-math
、-fno-protect-parens
、-fallow-store-data-races
、-fstack-arrays
、-fno-semantic-interposition
が利用できます。このオプションは規格への厳密な適合を犠牲にするため推奨されません。これは決してシステム全体で使用しないでください。このオプション付きでのソフトウェアの使用が監査されている場合に限り、パッケージ単位で使用してください。
-Os
: このレベルはバイナリのサイズを重視して最適化するでしょう。これは-O2
フラグの中で、生成されるバイナリのサイズが増えないものを全て有効にします。CPUのキャッシュが小さかったり、ディスクの空き容量が極端に限られている場合などに非常に有効でしょう。
-Oz
: GCC 12.1 で導入されたレベルで、-Os
よりもさらに積極的にサイズを最適化します。より少ないバイト数でエンコードさえできれば、実行される命令数を増やすこともあるため、-O2
と比較して激しく実行時パフォーマンスが劣化することに注意してください。
-Og
: GCC 4.8では新しい汎用的な最適化レベル-Og
が導入されました。このレベルはコンパイル時間の短縮とデバッグエクスペリエンスを向上させる一方、妥当なランタイム性能を確保することを目的としています。結果的に開発全体で得られるものはデフォルトの最適化レベル-O0
より向上するはずです。-Og
と-g
は同じでないことだけ理解しておいてください。-g
はデバッガとやりとりするために単純に最適化をオフにするだけです。
前述の通り、-O2
が推奨される最適化レベルです。もし-O2
以外を使用してパッケージのコンパイルが失敗する場合は、-O2
で再コンパイルしてみましょう。うまくいかなかった場合は、CFLAGSとCXXFLAGSを(エラー報告や問題の調査向けに)-O1
や-O0 -g2 -ggdb
のように、最適化レベルを低く設定してみてください。
-pipe
よく使うフラグに-pipe
があります。このフラグは、生成されるバイナリ自体には何の影響もありませんが、コンパイル時間が短縮されます。これはコンパイルにおける各処理の間で一時ファイルを使う代わりにパイプを使うように指示します。これにより多くのメモリを使うことになります。メモリに余裕のないシステムの場合、GCCが強制終了するかもしれません。そのような場合はこのフラグを使わないでください。
-fomit-frame-pointer
これは生成されるバイナリのサイズを減少させるために設計されているフラグで、よく利用されています。このフラグは、(x86-64のように)有効化してもデバッグ作業の阻害をしないアーキテクチャにおいては(-O0
を除く)全ての-O
のレベルに対して有効になりますが、有効化する必要がある場合もあります。そのような場合にはこれをフラグに加えてください。GCCマニュアルによれば、全てのアーキテクチャにおいて、-O
を使えば-fomit-frame-pointer
が有効になる訳ではありません。GCC 4.6までは、もしくは-Os
を使っているときは、x86-32で有効化するためには明示的に-fomit-frame-pointer
オプションを使わなければなりません。ただし、-fomit-frame-pointer
を使うとデバックは困難もしくは不可能になります。
特に、Javaで書かれgcjでコンパイルされたアプリケーションの問題解決をとても難しくします。もっとも、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
これらのフラグはストリーミング SIMD 拡張 (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です。VIAとAMD64はこれらの命令をサポートしますが、(SSE3のように)-march
では有効になりません。これらのCPUについては/proc/cpuinfoの出力を確認して、ふさわしいフラグを追加する必要があるでしょう。
x86とx86-64特有のフラグについてlist of x86 and x86-64-specific flagsを確認しましょう。適切にCPUを指定することによって、どの命令セットが有効になるのか確認することができます。もし命令セットがリストの中にあったら、改めて指定する必要はありません。なぜならそれらは正しい
-march
を使えば有効になるでしょうから。ハードニング最適化
これらの最適化のためには hardened プロファイルを使うことができる一方で、他のプロファイル上で /etc/portage/make.conf にハードニングフラグを追加するのは確実に不要です。特にデスクトップのシステムでは、システム全体のレベルで位置独立コード (PIC) と位置独立実行可能ファイル (PIE) を使用すると、コンパイルの失敗を引き起こすことがあります。
例えば desktop プロファイルを使用している場合など、特にハードニングされていないシステムをハードニングするのもまた、特に Meltdown や Spectre を踏まえると、考慮に値する GCC 最適化です。
パッケージによっては hardened
USE フラグを持つものがあり、テスト済みの (CFLAGS/CXXFLAGS などの) セキュリティ強化を有効化します。/etc/portage/make.conf 内でシステム全体にこれを設定するのはいい考えかもしれません。
基本的なハードニングがなされた CFLAGS/CXXFLAGS を確認するには、Hardened/FAQ の中の Do I need to pass any flags to LDFLAGS/CFLAGS in order to turn on hardened building? のセクションを読むのをおすすめします。
CFLAGS/CXXFLAGS の変更は問題を引き起こすことがあり、場合によってはシステムが不安定になるかもしれません。 emerge -e @world でシステム全体をリビルドすることで状況を解決できるかもしれません。
オーバーフロー保護
セキュリティ上の懸念が速度の最適化よりも重視される場合、オーバーフロー保護のために CFLAGS/CXXFLAGS を最適化することは良い考えでしょう。これは日常的に使用するデスクトップシステムに対しては適しているかもしれませんが、一方、最適化されたゲーミング PC 等では目的にそぐわないものでしょう。
GCC バージョン 12 では、パッケージ sys-devel/gcc の USE フラグ default-stack-clash-protection
および default-znow
を設定すると、自動的に追加のオーバーフロー保護が有効化されるでしょう。
現在 hardened プロファイルでは、これらのフラグの多くは自動で GCC ツールチェーンを通じて内部的に適用されており、一部は通常のプロファイルでも適用されています。Hardened/Toolchain の表を参照してください。
CFLAGS/CXXFLAGS | LDFLAGS | 機能 |
---|---|---|
-D_FORTIFY_SOURCE=2
|
実行時バッファオーバーフロー保護 | |
-D_GLIBCXX_ASSERTIONS
|
C++ 文字列およびコンテナに対する実行時境界チェック | |
-fstack-protector-strong
|
スタック破壊保護 | |
-fstack-clash-protection
|
信頼性を向上したスタックオーバーフロー検出 | |
-fcf-protection
|
コントロールフロー整合性保護 | |
-Wl,-z,defs
|
underlinking を検出し拒否する | |
-Wl,-z,now
|
lazy binding を無効化する | |
-Wl,-z,relro
|
リロケーション後にセグメントを読み込み専用にする |
ASLR
アドレス空間配置のランダム化 (ASLR) は、関数とバッファをそれぞれランダムにメモリ上に配置することで、セキュリティを向上させる手法です。これにより、攻撃ベクトルを成功させるのが難しくなります。
すべての 17.0 プロファイル上では、PIE を有効化しても安全な場合はデフォルトで有効化されています[3]。PIC もアーキテクチャによっては、実行可能ファイルがそれを必要とする場合 (AMD64 など) は、デフォルトで有効化されていることがあります。
CFLAGS に PIE または PIC を手動で設定する必要はありません。
CFLAGS/CXXFLAGS | LDFLAGS | 機能 |
---|---|---|
-fpie
|
-Wl,-pie
|
実行可能ファイルに完全な ASLR を適用する |
-fpic -shared
|
共有ライブラリでテキスト再配置を行わない |
最適化 FAQ
GCC のバージョンが新しいほど最適化も優秀なはずですよね?
ソフトウェアリグレッションのためにより古いバージョンの GCC で行われていた最適化が行われなくなることがあるので、そうとは限りません。リグレッションの完全な一覧はこのリンク先で確認できます。万一これが発生した場合は、Gentoo の bugzilla および/または GCC の bugzilla にバグを報告してください。
完璧なオプティマイザはないの?
ありません。それは任意のプログラムが停止するか永遠に実行し続けるかを判定することができるかという、停止性問題を解くのと同義になってしまうからです[4]。
GCC 自体の最適化はどう?
gcc には、それぞれプロファイルに基づく最適化とリンク時最適化を有効化する、pgo
と lto
USE フラグを持っています。gcc 自身のビルドのために PGO と LTO を有効化するには:
sys-devel/gcc pgo lto
Gentoo では、gcc の 3 ステージブートストラップが行われます。gcc は自身を 3 回コンパイルするということです[5]。ステージ 1 では、gcc が以前の gcc を使用してコンパイルされます。ステージ 2 では、gcc がステージ 1 の gcc を使用してコンパイルされます。ステージ 3 では、gcc がステージ 2 の gcc を使用してコンパイルされ、ステージ 2 の gcc とステージ 3 の gcc が同一であることを確認するために使用されます。これが行われるのは、こうすることでより完全にテストされるため、そしてより良いパフォーマンスが得られるためです。lto
USE フラグは BOOT_CFLAGS に -flto を追加します。pgo
USE フラグはステージ 2 の gcc に-fprofile-generate
を追加し、ステージ 4 の gcc に -fprofile-use -fprofile-reproducible=parallel-runs
を追加します。
gcc を PGO でコンパイルすると、コンパイル時間はほぼ倍になりますが、パフォーマンスは改善されるかもしれません。
-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
の最適化を行うだけで、それ以上の最適化はしません。
さらに証拠が必要ですか?ソースコードを確認してみてください。
case OPT_LEVELS_3_PLUS:
enabled = (level >= 3);
break;
case OPT_LEVELS_3_PLUS_AND_SIZE:
enabled = (level >= 3 || size);
break;
見てのとおり、3より高いレベルであっても、結局-O3
として扱われます。
実際のマシンと別のマシンでコンパイルするのはどう?
何人かの読者は、あきらかに劣ったCPUやGCCサブアーキテクチャでコンパイルすることを避けるために他のマシンでコンパイルすることは、(ネイティブな環境でのコンパイルと比較して)劣った最適化になるのか知りたくなるでしょう。答えは単純でいいえです。コンパイルが走る実際のマシンに関係なく、またGCCをビルドしたときのCHOSTに関係なく、同じ引数が使用されている限り(-march=native
は除く)、そしてGCCのバージョンが同じである限り(マイナーバージョンが違うかもしれません)、最適化の結果は厳密に同じです。
例を一つあげます。GCC の CHOST が i686-pc-linux-gnu となっているマシンに Gentoo をインストールして、CHOST が i486-linux-gnu となっている別の PC に Distcc サーバーをインストールします。リモートサーバーのコンパイラのサブアーキテクチャもしくはハードウェアが明らかに劣っている場合、最適化が十分なされないのではと心配する必要はありません。結果は、両方のコンパイラに同じ引数が与えられている限り(かつ -march
に native
が与えられていない限り)、ネイティブにビルドしたときと同じ最適化がかかります。ただしこの特殊なケースでは Distcc で説明されている通り、ターゲットのアーキテクチャが明示的に指定されなければなりません。
異なるサブアーキテクチャに向けてビルドされた2つのGCCの動作には一つしか違いがありません。それは暗に与えられるデフォルトの-march
です。コマンドラインで-march
を明示的に指定しなかった場合、GCCのCHOSTに設定された値が使用されます。
冗長なフラグ指定はどう?
しばしば、/etc/portage/make.confの中で、個々の-O
レベルを指定すれば有効になるフラグを重複してCFLAGSやCXXFLAGSに設定していることがあります。これは時々知らずにやってしまうのですが、一方で(ebuildが行う)フラグの除去や置換を回避するために意図的に行われることがあります。
フラグの除去や置換はPortageツリーの中にある多くのebuildで行われます。大抵は、特定の-O
レベルでパッケージをコンパイルすると失敗するために、もしくはフラグを追加するとそのソースコードでは問題が出るためです。ebuildはどちらの場合も、全部/一部のCFLAGSとCXXFLAGSを除外するか、もしくは異なる-O
レベルに置換するでしょう。
Gentoo Developer Manualにフラグの除去と置換がどのような場合に、どのように使われているのか概要が記載されています。
特定のレベルに対して重複してフラグを設定することによって、-O
に対するフラグ除去をある程度回避することができます。例えば、-O3
であれば次のようにします。
CFLAGS="-O3 -finline-functions -funswitch-loops"
しかしながら、これは賢いやり方ではありません。CFLAGSは理由があって除去されるのです! フラグが除去されるとき、それらのフラグでパッケージをビルドすると安全でないことを意味します。明らかに分かっているのは、-O3
でシステム全体をコンパイルすることは"安全ではない"という事です。そうすると、-O3
の最適化で有効になるいくつかのフラグによって問題となるパッケージが出てくるでしょう。そのため、それらのパッケージをメンテナンスしている開発者の"裏をかく"ことを試みないでください。
開発者を信用してください。フラグの除去と置換はシステムやアプリケーションの安定性を確保するために行われているのです! もしebuildに代替のフラグが指定されているなら、それを回避しようとしないでください。
開発者が許可していないフラグでパッケージをビルドすれば、大概は数々の問題に繋がるでしょう。Bugzillaに不具合を報告する際には、/etc/portage/make.confで使っているフラグは容易く見抜かれてしまうので、余計なフラグを除いて再コンパイルする様に開発者に指示されるでしょう。初めから冗長なフラグを指定しないことで、再コンパイルする手間を省いてください! あなたが開発者よりよく知っていると根拠なく無意識に決めつけないでください。
LDFLAGS はどう?
Gentoo開発者がすでに基本的で安全なLDFLAGSを基本プロファイルにセットしているので、それらを変更する必要はありません。
パッケージごとにフラグを変更出来るの?
パッケージごとにフラグを変更するとデバッグやサポートが込み入ってきます。もしこの機能を使っている場合には、どのような変更をしたのか、バグレポートで言及してください。
パッケージごとに (CFLAGS を含む) 環境変数を変更する方法は、Gentoo ハンドブックの「パッケージごとの環境変数」で説明しています。
プロファイルに基づく最適化 (PGO)
プロファイルに基づく最適化 (PGO) は、まずプログラムをコンパイルし、その後コードのホットパスを調査するためのプロファイリングの 2 ステップから成ります。この解析結果に基づいて、最適化が行われます。具体的には、コードはまず -fprofile-generate
付きでコンパイルされ、コードを計測します。次に、プロファイル情報を収集するために、アプリケーションとともにコードが実行されます。最後に、プロファイルデータを使用して、コードが -fprofile-use
付きでコンパイルされます。パッケージごとに PGO を手動で有効化するには、このリンクを参照してください。
Firefox も PGO をサポートしていますが、ときどきビルドできないことがあります。
リンク時最適化 (LTO)
LTO はコンパイル時間を大きく増加させ、たとえオブジェクトファイルを 1 個変更しただけでも、LTO はコード全体をふたたびコンパイルします。LTO が必要とみなされるもののみを再コンパイルするようにする、進行中の GSoC プロジェクトがあります。
LTO はまだ実験的な機能です。LTO はよく問題の原因となるので、バグを報告する前に無効化する必要があるかもしれません。LTO を実施するためには、-flto
フラグが省略可能な auto
引数 (何個のジョブを使用するかを検出します) とともに、または整数引数 (並列に実行するジョブ数を表す整数) とともに使用されます。
Gentoo 上での LTO に関するさらなる情報については、LTO の記事を参照してください。
関連項目
- コンパイルオプションを設定する (AMD64 ハンドブック)
- CPU_FLAGS_* — CPU の命令セットや他の特殊機能を含む USE_EXPAND 変数です。
- Safe CFLAGS — Gentoo Linux 上での CFLAGS の「安全な」設定のまとめ
- RUSTFLAGS
外部資料
以下、最適化について理解を深めるための資料を紹介します。
参照
- ↑ GNU GCC Bugzilla, AVX/AVX2 で単純約分のときに XMM レジスタが使われない問題。2017/07/18 取得。
- ↑ GNU GCC Bugzilla, 'gcc -marc=native' sets L2 cache size equal to L3 cache size on i7 and i5 CPU. 2022/08/14 取得。
- ↑ New 17.0 profiles in the Gentoo repository
- ↑ https://en.wikipedia.org/wiki/Full-employment_theorem
- ↑ https://gcc.gnu.org/install/build.html
This page is based on a document formerly found on our main website gentoo.org.
The following people contributed to the original document:
They are listed here because wiki history does not allow for any external attribution. If you edit the wiki article, please do not add yourself here; your contributions are recorded on each article's associated history page.