コンパイラオプションによる自動最適化について解説します.
● -0,-01
このオプションでは基本的な最適化を行います.
たとえばregisterの不要な移行を行わない,メモリアクセスを減らすためにregisterに値を保存しておくなどの最適化を行います.
最適化を行うと,コンパイルには時間がかかり,大きな関数についてはたくさんのメモリを余計に使います.
-0を指定しないと,register宣言した変数しかレジスタに割り当てません.
-0を指定することでコードサイズと実行時間を小さくしようとします.また,全機種で-fthread-jumpsと-fdefer-popを有効にします.遅延スロットのある機種では-fdelayed-branchをオンにし,フレームポインタなしでもデバッグをサポートできる機種では-fomit-frame-pointerをオンにします.機種によっては,ほかのオプションをオンにするものもあります.
フレームポインタに関して補足すると,C/C++では関数が呼び出された際に,スタックに積まれた引き数を指し示すためのフレームポインタを設定しています.しかし,このフレームポインタは省略可能であり,そうすることによりバイナリの最適化が図れます.ただし,デバッガを使ってのバイナリファイルのデバッグは不可能になります.
● -02
コンパイラはほとんどすべての最適化を実行します.-02を指定した場合,ループ展開や関数のインライン展開を行いません.-0と比べると,このオプションはコンパイル時間が増えますが,生成コードの効率が良くなります.
-02を指定すると,ループ展開と関数のインライン展開,それに厳密なエイリアシングを除いたすべての最適化が有効になります.また,すべての機種で-fforce-memオプションを付け,デバッグと干渉しない機種ではフレームポインタの削除を行います.
● -03
-03は,-02で指定されるすべての最適化を行い,かつinline-functionsオプションを行います.inline-functionsオプションについては後述します.
● -00
最適化を行いません.gdbが正しく動作しない場合には,このオプションを付けることでデバッグができます.
● -0s
サイズについて最適化します.-02の最適化をすべて有効にし,結果としてサイズが大きくなる最適化をしなければならない場合には最適化を中止します.
-0オプションを複数指定した場合は,最適化レベルの数字が付いていてもいなくても,最後に指定したオプションが有効になります.
-fflagという形のオプションは機種独立のフラグを指定します.ほとんどのフラグには,肯定形と否定形があります.
-ffooの否定形は-fno-fooです.
● -ffloat-store
浮動小数点変数をレジスタにストアしないようにし,浮動小数点値をレジスタかメモリから取り出すかどうかを変更する可能性のあるオプションを禁止します.
このオプションを指定すると,68000のようなプロセッサで好ましくない余分な精度を使わないようになります.68000では浮動小数点レジスタは,doubleがもつと想定されているよりも余分の精度を保持しています.インテルのCPUについても同様です.いくつかのプログラムはIEEE浮動小数点の厳密な定義を想定していて.そういうプログラムに対しては,適当な中間計算結果をすべて変数に格納するようにプログラムを修正しておいて,-ffloat-storeを使うことができます.
Cのdouble型は64ビットを符号1ビット/指数部11ビット/仮数部52ビットとして浮動小数点形式を表現しています.double型では,仮数部は2の53乗の数を表現することができますが,たいていのCPUでは,浮動小数点レジスタの精度は80ビットあります.その分の精度が無駄だと思う場合,-ffloat-storeのオプションを選択します.
x86アーキテクチャではこのオプションを選択しない場合,多少メモリアクセスが増加する可能性がありますが,筆者の考えではこのオプションは指定しないほうが良いと思います.どの最適化オプションでもそうなのですが,可搬性と実行速度を秤にかけることになります.ただしROM上で動くものを作る場合,可搬性を犠牲にしてサイズと実行速度を追求すると思います.そんなときに試行錯誤し,吐き出したアセンブラソースを見て最適化を検討することが必要となります.
● -fno-defer-pop
関数呼び出しのたびに,常にその関数から戻るとすぐ引き数をPOPします.関数呼び出しの後に引き数をPOPしなければならない機種では,GCCは普通は複数の関数呼び出しについてスタックに引き数を蓄積し,それらをすべて一度にPOPします.
これはハードウェアのアドレスを共有して実行するような場合や,そのオブジェクトの永続性が短い関数から戻った場合などに指定するべきオプションです.
● -fforce-mem
メモリオペランドについて算術演算を行う前に,そのメモリオペランドをレジスタに強制的にコピーさせます.この結果,すべてのメモリ参照を潜在的な共通部分式とすることにより,生成コードが良くなる可能性があります.これらのメモリ参照が共通部分式でない場合は,命令組み合わせフェーズが独立したレジスタへのロードを削除する必要があります.-02を指定すると,このオプションが有効になります.
筆者には,このオプションは可搬性に有害なだけで最適化の意味があまりないように思えます.
● -fforce-addr
メモリ中のアドレス定数を,それについて算術演算を行う前にレジスタにコピーします.処理速度が多少速くなる場合があります.
メモリ中の数値を演算するよりレジスタに格納された数値を演算するほうがもちろん速くなります.このオプションは試してみる価値があると思います.
● -fomit-frame-pointer
フレームポインタを必要としない関数について,フレームポインタをレジスタにもちません.これにより,フレームポインタをセーブ,設定,リストアする命令をなくすことができ,多くの関数で利用可能なレジスタが一つ増えます.しかし,機種によってはデバッグが不可能になってしまいます.
フレームポインタを扱わないことについてユーザーがわかっているのであれば,コードの無駄が省かれるので,試す価値のあるオプションだと思います.
● -fno-inline
キーワードinlineを無視します.このオプションは,関数のインライン展開をいっさい行わせないために使われます.最適化を行わない場合には,どの関数もインライン展開されません.こうした最適化の方法は,ソースに書かれたものを無視するので混乱の元になる可能性があります.
キーワードinlineを無視したいのであれば,ソース上で実際にキーワードinlineを削除したほうが可読性も高まり,可搬性も高まると思います.
● -finline-functions
単純な関数をすべて呼び出し元のコードに置きます.ある関数に対するすべての呼び出しが統合される場合,その関数がstaticと宣言されていれば,普通はその関数はアセンブラコードとしては出力されません.
このオプションで指定するのも良いと思いますが,キーワードinlineを指定したほうが可読性も高まり,可搬性も高まると思います.
● -fkeep-inline-functions
ある関数に対する呼び出しが全部統合され,その関数がstaticと宣言されている場合でも,実行時に呼び出し可能な関数を別個に出力します.このオプションは,extern inline宣言された関数には影響しません.
このオプションに関しては,混乱をまねくかもしれないので注意が必要です.ソース上ではプログラマが意図しているのがstaticなのであればそれにしたがわないと障害が起きる可能性があります.
● -fno-function-cse
関数のアドレスをレジスタに置かないようにします.ある関数を呼び出す命令は,それぞれ関数のアドレスを明示的に保持するようにします.
このオプションは効率の低いコードを生成しますが,アセンブラ出力ソースを書き換えるようなことを行う場合には,このオプションを使用しなければ混乱してしまいます.
● -ffast-math
このオプションを指定すると,実行速度を最適化するという観点から,ある面でANSIやIEEEの規則や仕様を破ることをコンパイラに許します.たとえば,このオプションを指定すると,コンパイラは,sqrt関数の引き数が負にならないとか,浮動小数点値がNaNになることはないという仮定を行います.
このオプションは大前提を崩す恐れがあるので,使用しないほうが良いと思います.
|