最適化(コードの改善)

-O*オプションは便利な最適化フラグの「詰め合わせ」を指定するのに使う。後で説明される-f*オプションは個々の最適化を有効/無効にするのに使う。-m*機械固有の最適化を有効/無効にするのに使う。

-O*: 便利な最適化フラグの「詰め合わせ」。

GHCが生成するコードの質に影響を与えるオプションは大量にある。大抵の人には一般的な目標しかない。つまり、「素早くコンパイルすること」であるとか「電光石火のように走るプログラムを生成すること」などである。以下に示す最適化の「詰め合わせ」を指定すれば(あるいは指定しないことを選べば)十分なはずである。

注意点として、高い最適化水準では多くのモジュール間最適化が行われ、何かを変更したときにどの程度再コンパイルが必要かに影響を与える。これは開発中に非最適化を貫くことの理由の一つである。

-O*が指定されないとき:

「なるべく速くコンパイルしてほしい。できたコードの品質についてはうるさくいわない」という意味にとられる。例えば、ghc -c Foo.hsのような場合である。

-O0:

「全ての最適化を無効にせよ」という意味であり、-Oが指定されていないかのような状態に戻す。-O0は、例えばmakeが既に-Oをコマンド行に挿入しているときに便利である。

-Oまたは-O1:

「高品質のコードをそれほど時間を掛けずに生成せよ」という意味である。例えば、ghc -c -O Main.lhsのように使われる。

-O2:

「危険でない全ての最適化を適用せよ。コンパイルに非常に時間が掛かっても構わない」という意味である。

避けられる「危険」な最適化とは、運が悪いときに実行時間・空間を悪化させるおそれのあるものである。通常これらは個々に設定される。

現時点では、-O2-Oよりも良いコードを生成することは考えにくい。

我々は日々の作業では-O*を使わない。それなりの速度が必要なときは-Oを使う。例えば、何かを計測するときなどである。「とにかくハイクオリティがいいお( ^ω^) 時間とか気にしないお!CPU稼働率100%でも構わないお!」というあなたには、-O2オプションをどうぞ。ガリガリ音を立てるPCを後にして、コーヒーを100万回飲みに行こう。(コーヒーブレイクするってレベルじゃねぇぞ!)

-O(など)が何を「実際に意味している」かを知るもっとも簡単な方法は、-vを付けて走らせ、驚きのあまり後ずさることである。

-f*: プラットフォーム非依存のフラグ

これらのフラグは個々の最適化を有効・無効にする。これらは通常、上記の-O系のオプションを介して設定され、したがって、どれも明示的に指定する必要はないはずである。(実際、そうすると予期せぬ結果が訪れるかもしれない)。しかし、一・二の興味が湧く例はあるかもしれない。

-fexcess-precision:

このオプションが与えられると、中間の浮動小数点数が最終的な型よりも大きな精度/範囲をもつことが許される。一般にはこれは良いことだが、Float/Doubleの正確な精度/範囲に依存したプログラムがあるかもしれず、そのようなプログラムはこのオプションなしでコンパイルせねばならない。

-fignore-asserts:

ソースコード中でException.assert関数が使われていても無視する。(言い替えると、Exception.assert p eeに書き換える。7.12. アサーション を見よ)。このフラグは-Oが指定されていると有効になる。

-fno-cse

共通部分式削除の最適化を無効にする。共通化されてほしくないunsafePerformIOを使った式があるときに便利だろう。

-fno-strictness

正格性解析器を無効にする。正格性解析器はあまりに多くのマシンサイクルを消費することがある。

-fno-full-laziness

完全遅延(full laziness)最適化(let-floatingとしても知られる)を無効にする。完全遅延は共有を促進するが、これはmemory residency(訳注: 意味不明)を増やすことにつながる。

注意: GHCは完全遅延を完全には実装していない。最適化が有効で、-fno-full-lazinessが与えられなかったとき、共有を促進するある種の最適化が実行される。例えば繰り返し実行される計算をループから抽出する、といったことである。これらは完全遅延の実装で行われるのと同じ変換だが、GHCは常に完全遅延を適用するとは限らないという違いがあるので、これに頼らないこと。

-fno-float-in

float-in変換を無効にする。

-fno-specialise

多重定義関数の自動特殊化を無効にする。

-fspec-constr

呼び出しパターンの特殊化(call-pattern specialization)を有効にする。

-fliberate-case

liberate-case変換を有効にする。

-fstatic-argument-transformation

static argument変換を有効にする

-fomit-interface-pragmas

コンパイル中のモジュール(Mとしよう)のインタフェースファイルにおいて、本質的でない情報を全て省略するようにGHCに指示する。これによって、Mをインポートするモジュールからは、Mがエクスポートする関数のしか見えず、展開候補や正格性情報などが見えなくなる。よって、例えば、Mからエクスポートされた関数が、それをインポートするモジュールでインライン化されるということがなくなる。これによる利点は、Mをインポートするモジュールを再コンパイルしなければならない頻度が減るということである。(Mのエクスポート物の型が変わった場合だけ再コンパイルすればよく、実装が変わっただけならしなくてよい)

-fignore-interface-pragmas

インタフェースファイルを読むときに本質的でない情報を全て無視するようにGHCに指示する。つまり、仮にM.hiに関数の展開候補や正格性情報があったとしても、GHCはその情報を無視する。

-funbox-strict-fields:

このオプションは正格と印の付けられた(つまり「!」)構築子フィールドを可能なら全て非ボックス化、つまりアンパックする。これは全ての正格な構築子フィールドにUNPACKプラグマを付けるのと同等である。(7.13.10. UNPACKプラグマを見よ)

このオプションは少々大槌を振り回す感じがある。場合によっては状況を悪化させかねない。UNPACKを使ってフィールドを選択的に非ボックス化する方が良いかもしれない。

-funfolding-creation-threshold=n:

(デフォルト: 45)関数の展開候補(unfolding)に許される最大の大きさを定める。(展開候補には、それが呼び出し点で展開されたときの「コード膨張」のコストを反映した「大きさ」が与えられる。大きい関数ほど大きなコストを持つ)

これによる影響は、(a)これより大きい物は(INLINEプラグマがない限り)決してインライン展開されない (b)これより大きい物は決してインタフェースファイルに吐かれることはない、という点である。

この数値を増やしても、結果としてコードが速くなるというよりは単にコンパイル時間が長くなる公算が高い。次のオプションの方が便利である。

-funfolding-use-threshold=n:

(デフォルト: 8)これは展開にあたっての魔法のカットオフ値である。これより小さい関数定義は呼び出し元に展開され、これより大きい物は展開されない。関数の大きさは、式の実際の大きさに、適用される割引を加味したものである。(-funfolding-con-discountを見よ)