GCCの最適化

From Gentoo Wiki
Jump to: navigation, search
This page is a translated version of the page GCC optimization and the translation is 75% complete.

Other languages:
English • ‎español • ‎français • ‎italiano • ‎日本語 • ‎한국어 • ‎português do Brasil • ‎русский • ‎Türkçe

このガイドは、コンパイル済みコードを安全で分別のあるCFLAGSCXXFLAGSを使って最適化する手法を紹介します。また、一般的な最適化の背景にある理論について述べます。

はじめに

CFLAGSとCXXFLAGSとは?

CFLAGSCXXFLAGSは、CやC++のソースコードをコンパイルするときに使われるオプションを、コンパイラに指示するために慣例的に使われる環境変数の一種です。これらの環境変数は標準化されているわけではありませんがそれに近い状況で用いられており、コンパイラを使う際に追加のオプションを指定したいなら理解しておく必要があります。GNU makeでは一般的によく使われている環境変数のリストが記されています。

Gentooシステムでは大多数のパッケージがCまたはC++で書かれているため、これらの環境変数はシステムがどのように構成されるかに多大な影響を及ぼします。そのため管理者にはこれらを正しく設定することが求められます。

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

どのように使われているのでしょうか?

普通CFLAGSCXXFLAGSは、configureスクリプトを走らせるか、プログラム毎にautomakeにより生成されたMakefileによってセットされます。Gentooシステムでは、CFLAGSCXXFLAGS/etc/portage/make.confで設定します。このファイルに定義された変数はportageが呼び出したプログラムに適用され、その設定に基づいてプログラムがコンパイルされます。

CODE /etc/portage/make.confのCFLAGS設定
CFLAGS="-march=athlon64 -O2 -pipe"
CXXFLAGS="${CFLAGS}"
重要
USEフラグは複数の行で構成できますが、CFLAGSを複数の行で構成するとcmakeのようなプログラムで問題が発生します。問題を避けるために、CFLAGSの宣言は極力スペースで区切られた単一行で構成するようにしてください。bug #500034が参考になります。

見てわかるとおり、CXXFLAGSCFLAGSの中にある全てのフラグが設定されています。大部分のシステムはこのように設定されるべきです。通常のケースでは、CXXFLAGSだけに追加のオプションを指定するようなことはほとんどありませんし、大体そういったオプションはグローバルに適用すべきではありません。

Tip
安全なCFLAGS 初心者向けにシステム最適化の方法が書かれています。

よくある誤解

CFLAGSでコンパイラの最適化をするのはより小さく高速なバイナリを生成するには効果的な方法ですが、一方で正しく動作しない、サイズの巨大化や動作速度の低下を引き起こしたり、そもそもコンパイルできないなどの問題を起こす可能性があります。CFLAGSを弄っているとこうしたことはあっさりと発生します。適当に設定してはいけません。

/etc/portage/make.confにて設定したCFLAGSはシステム上の全てのパッケージに適用されます。そのため管理者は至極一般的で広く適用しても問題ないオプションのみを設定するのが普通です。これに加えて各々のパッケージがebuildやコンパイル中にさらに必要なオプションを自動で追加しています。

準備はできましたか?

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

最適化について

基本

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

注意
フラグがどういう働きをするのか不明なときは、GCC manualの関連する章を参照してください。もしそれでもわからない場合は、ウェブ検索をするか、GCC mailing listsをチェックしましょう

-march

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

-march=はISA(訳註:命令セットアーキテクチャ)指定のオプションで、コンパイラにどんな命令が利用可能かを伝えます。Intel/AMD64プラットフォームで -march=native</code <code>-O2を使用すると、AVX命令はサポートされますがSSEのXMMレジスタがサポートされない場合があります。AVXのYMMレジスタを最大限利用したい場合、-ftree-vectorize -O3または-Ofast も指定する必要があります。[1]

-ftree-vectorize is an optimization option (default at -O3 and -Ofast), which attempts to vectorize loops using the selected ISA if possible. The reason it isn't enabled at -O2 is that it doesn't always improve code, it can make code slower as well, and usually makes the code larger; it really depends on the loop etc.

Even though the CHOST variable in /etc/portage/make.conf specifies the general architecture used, -march should still be used so that programs can be optimized for the system specific processor. x86 and x86-64 CPUs (among others) should make use of the -march flag.

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

user $cat /proc/cpuinfo

or even install app-portage/cpuid2cpuflags and add the available CPU-specific options to the make.conf file, which the tool does through e.g. the CPU_FLAGS_X86 variable:

root #cpuinfo2cpuflags-x86
CPU_FLAGS_X86: aes avx avx2 f16c fma3 mmx mmxext popcnt sse sse2 sse3 sse4_1 sse4_2 ssse3
root #echo "CPU_FLAGS_X86='aes avx avx2 f16c fma3 mmx mmxext popcnt sse sse2 sse3 sse4_1 sse4_2 ssse3'" >> /etc/portage/make.conf

さらにmarchmtuneの値を含む詳細な情報を得たい場合は、以下の2つのコマンドが使えます。

  • 最初のコマンドはコンパイラにリンクを行わないようにさせ(-c)、また、--helpをコマンドラインオプションを明らかにせよとではなく、あるオプションが有効か無効かを示せという意味に解釈させます(-Q)。ここでは、選択されたターゲットで有効なオプションが出力されます:
user $gcc -c -Q -march=native --help=target
  • The second command will show the compiler directives for building the header file, but without actually performing the steps and instead showing them on the screen (-###). The final output line is the command that holds all the optimization options and architecture selection:
user $gcc -### -march=native /usr/include/stdlib.h

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

FILE /etc/portage/make.confPentium IIIの例
CFLAGS="-march=pentium3"
CXXFLAGS="${CFLAGS}"

こちらはAMD64向けです。

FILE /etc/portage/make.confAMD64の例
CFLAGS="-march=athlon64"
CXXFLAGS="${CFLAGS}"

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

警告
distccを使ってコンパイルする場合、make.confCFLAGSもしくはCXXFLAGS-march=native-mtune=nativeを使用してはいけません

If compiling packages on one computer in order to run them on a different computer (such as when using a fast computer to build for an older, slower machine), then do not use -march=native. "Native" means that the code produced will run only on that type of CPU. The applications built with -march=native on an AMD Athlon 64 CPU will not be able to run on an old VIA C3 CPU.

また、-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のシステム上では非推奨となっているので、使わないでください。

Only non-x86/x86-64 CPUs (such as SPARC, Alpha, and PowerPC) may require -mtune or -mcpu instead of -march. On these architectures, -mtune / -mcpu will sometimes behave just like -march (on x86/x86-64) but with a different flag name. Again, GCC's behavior and flag naming is not consistent across architectures, so be sure to check the GCC manual to determine which one should be used.

注意
更なる-march/-mtune/-mcpuの設定についての情報は、あなたのアーキテクチャに適したGentooハンドブックの5章を読んでみてください。また、GCCマニュアルのarchitecture-specific optionsのリストに、-march-mcpu-mtuneの違いについてもっと詳しい説明があります。

-O

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

There are seven -O settings: -O0, -O1, -O2, -O3, -Os, -Og, and -Ofast. Only use one of them in /etc/portage/make.conf.

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

では、それぞれの最適化レベルを見てみましょう。

  • -O0: このレベル("O"のあとにゼロが続いてます)は、完全に最適化をオフにします。CFLAGSCXXFLAGSの中に-Oが定義されていない場合のデフォルトです。このレベルはコンパイル時間を短縮して、生成するデバッグ情報を改善しますが、いくつかのアプリケーションは最適化がないと正しく動作しません。よって、デバッグ以外では推奨されません。
  • -O1: これは最も基本的な最適化レベルです。コンパイラは時間をたくさんかけることなく、高速でサイズの小さなバイナリを生成しようと試みるでしょう。これは基本的な最適化しかおこないませんが、その代わり、いつでもうまくいくはずです。
  • -O2: A step up from -O1. The recommended level of optimization unless the system has special needs. -O2 will activate a few more flags in addition to the ones activated by -O1. With -O2, the compiler will attempt to increase code performance without compromising on size, and without taking too much compilation time. SSE or AVX may be be utilized at this level but no YMM registers will be used unless -ftree-vectorize is also enabled.
  • -O3: the highest level of optimization possible. It enables optimizations that are expensive in terms of compile time and memory usage. Compiling with -O3 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. -O3 is also known to break several packages. Using -O3 is not recommended. However, it also enables -ftree-vectorize so that loops in the code get vectorized and will use AVX YMM registers.
  • -Os: このレベルはバイナリのサイズを重視して最適化するでしょう。これは-O2フラグの中で、生成されるバイナリのサイズが増えないものを全て有効にします。CPUのキャッシュが小さかったり、ディスクの空き容量が極端に限られている場合などに非常に有効でしょう。
  • -Og: GCC 4.8では新しい汎用的な最適化レベル-Ogが導入されました。このレベルはコンパイル時間の短縮とデバッグエクスペリエンスを向上させる一方、妥当なランタイム性能を確保することを目的としています。結果的に開発全体で得られるものはデフォルトの最適化レベル-O0より向上するはずです。-Og-gは同じでないことだけ理解しておいてください。-gはデバッガとやりとりするために単純に最適化をオフにするだけです。
  • -Ofast: GCC 4.7では、-O3に加えて-ffast-math-fno-protect-parens-fstack-arraysが利用できます。このオプションは規格への厳密な適合を犠牲にするため推奨されません。

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

-pipe

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

-fomit-frame-pointer

This is a very common flag designed to reduce generated code size. It is turned on at all levels of -O (except -O0) on architectures where doing so does not interfere with debugging (such as x86-64), but it may need to be 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 -O option. It's still necessary to explicitly enable the -fomit-frame-pointer option, to activate it on x86-32 with GCC up to version 4.6, or when using -Os on x86-32 with any version of GCC. However, using -fomit-frame-pointer will make debugging hard or impossible.

特に、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

These flags enable the Streaming SIMD Extensions (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.

Important
Be sure to see if the CPU supports these instruction sets by running cat /proc/cpuinfo. The output will include any supported additional instruction sets. Note that pni is just a different name for 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を使えば有効になるでしょうから。

最適化FAQ

-funroll-loopsや-fomg-optimizeを使ったら速くなったんだけど!

No, people only think they do because someone has convinced them that more flags are better. Aggressive flags will only hurt applications when used system-wide. Even the GCC manual says that using -funroll-loops and -funroll-all-loops will make code larger and run more slowly. Yet for some reason, these two flags, along with -ffast-math, -fforce-mem, -fforce-addr, and similar flags, continue to be very popular among ricers who want the biggest bragging rights.

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

These flags are not needed globally in CFLAGS or CXXFLAGS. They will only hurt performance. They might bring on the idea of having a high-performance system running on the bleeding edge, but they don't do anything but bloat the code and get bugs marked INVALID or WONTFIX.

Dangerous flags like these are not needed. Don't use them. Stick to the basics: -march, -O, and -pipe.

3より高い-Oレベルはどう?

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

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

CODE -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として扱われます。

実際のマシンと別のマシンでコンパイルするのはどう?

何人かの読者は、あきらかに劣ったCPUやGCCサブアーキテクチャでコンパイルすることを避けるために他のマシンでコンパイルすることは、(ネイティブな環境でのコンパイルと比較して)劣った最適化になるのか知りたくなるでしょう。答えは単純でいいえです。コンパイルが走る実際のマシンに関係なく、またGCCをビルドしたときのCHOSTに関係なく、同じ引数が使用されている限り(-march=nativeは除く)、そしてGCCのバージョンが同じである限り(マイナーバージョンが違うかもしれません)、最適化の結果は厳密に同じです。

例を一つあげます。GCCのCHOSTがi686-pc-linux-gnuとなっているマシンにGentooをインストールして、CHOSTがi486-linux-gnuとなっている別のPCにDistccサーバーをインストールします。リモートサーバーのコンパイラのサブアーキテクチャもしくはハードウェアが明らかに劣っている場合、最適化が十分なされないのではと心配する必要はありません。結果は、両方のコンパイラに同じ引数が与えられている限り(かつ-marchnativeが与えられていない限り)、ネイティブにビルドしたときと同じ最適化がかかります。ただしこの特殊なケースではDistcc and -march=nativeで説明されている通り、ターゲットのアーキテクチャが明示的に指定されなければなりません。

異なるサブアーキテクチャに向けてビルドされた2つのGCCの動作には一つしか違いがありません。それは暗に与えられるデフォルトの-marchです。コマンドラインで-marchを明示的に指定しなかった場合、GCCのCHOSTに設定された値が使用されます。

冗長なフラグ指定はどう?

しばしば、/etc/portage/make.confの中で、個々の-Oレベルを指定すれば有効になるフラグを重複してCFLAGSCXXFLAGSに設定していることがあります。これは時々知らずにやってしまうのですが、一方で(ebuildが行う)フラグの除去や置換を回避するために意図的に行われることがあります。

フラグの除去や置換はPortageツリーの中にある多くのebuildで行われます。大抵は、特定の-Oレベルでパッケージをコンパイルすると失敗するために、もしくはフラグを追加するとそのソースコードでは問題が出るためです。ebuildはどちらの場合も、全部/一部のCFLAGSCXXFLAGSを除外するか、もしくは異なる-Oレベルに置換するでしょう。

Gentoo Developer Manualにフラグの除去と置換がどのような場合に、どのように使われているのか概要が記載されています。

特定のレベルに対して重複してフラグを設定することによって、-Oに対するフラグ除去をある程度回避することができます。例えば、-O3であれば次のようにします。

CODE 重複してCFLAGSを設定
CFLAGS="-O3 -finline-functions -funswitch-loops"

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 the whole system with -O3 if some of the flags turned on by that level will cause problems with certain packages. Therefore, don't try to "outsmart" the developers who maintain those packages. Trust the developers. Flag filtering and replacing is done to ensure stability of the system and application! If an ebuild specifies alternative flags, then don't try to get around it.

Building packages with unacceptable flags will most likely lead to problems. When reporting problems on Bugzilla, the flags that are used in /etc/portage/make.conf will be readily visible and developers will ask to recompile without those flags. Save the trouble of recompiling by not using redundant flags in the first place! Don't just automatically assume to be more knowledgeable than the developers.

LDFLAGSはどう?

Gentoo開発者がすでに基本的で安全なLDFLAGSを基本プロファイルにセットしているので、それらを変更する必要はありません。

パッケージごとにフラグを変更出来るの?

Warning
Using per-package flags complicates debugging and support. Make sure to mention the use of this feature in the bug reports together with the changes made.

パッケージごとに(CFLAGSを含む)環境変数を変更する方法は、Gentoo Handbook, "Per-Package Environment Variables"で説明しています。

See also

External resources

以下、最適化について理解を深めるための資料を紹介します。

  • man make.conf

References


This article is based on a document formerly found on our main website gentoo.org.
The following people contributed to the original document: Joshua Saddler (nightmorph)
They are listed here as the 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 the history page.