4.10. 最適化(コードの改善)

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

4.10.1. -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を付けて走らせ、驚きのあまり後ずさることである。

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

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

-fexcess-precision:

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

-fignore-asserts:

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

-fignore-interface-pragmas

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

-fliberate-case

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

-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

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

-fno-state-hack

State#トークンを持つラムダを、単一進入であり、したがってその中に物をインライン化しても良いとみなす「stateハック」を無効にする。これはIOおよびSTモナドのコードの性能を向上させ得るが、共有を減らす危険を冒している。

-fpedantic-bottoms

GHCがボトムをより精密に扱うようにする(しかし、-fno-state-hackも見よ)。特に、case式を透過してイータ展開をすることがなくなる。このようなイータ展開は性能には良いが、部分適用の結果に対してseqを使っているなら悪いものになる。

-fomit-interface-pragmas

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

-fsimpl-tick-factor=n

GHCの最適化器は、停止しない書き換え規則(7.17. 書き換え規則 )を書いたとき、または(もうすこし嫌なことに)データ型を通して再帰を組み上げた場合(13.2.1. GHCのバグ)に発散する。コンパイラが無限ループに陥るのを避けるため、最適化器は「tickの回数」を保持し、この回数が超過したときにはインライン化と書き換え規則の適用をやめる。大きいプログラムが多くのtickを使えるように、この限界はプログラムの大きさの定数倍になる。-fsimpl-tick-factorはこの定数を変えられるようにする。デフォルトは100である。100より大きな数はより沢山のtickを与え、100より小さい数はより少ないtickを与える。

tickの数が尽きると、GHCはそれまでに実行した単純化器の歩みを要約する。-fddump-simpl-statsを使うとずっと詳細な一覧を生成することができる。通常これでループをかなり精密に同定することができる。いくつかの数がとても大きくなるからである。

-fstatic-argument-transformation

static argument変換を有効にする

-fspec-constr

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

-funbox-strict-fields:

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

このオプションは少々大槌を振り回す感じがある。場合によっては状況を悪化させかねない。UNPACKを使ってフィールドを選択的に非ボックス化する方が良いかもしれない。もう一つの選択肢は、-funbox-strict-fieldsを使ってデフォルトで非ボックス化を有効にしつつ、特定の構築子フィールドについてはNOUNPACKプラグマを使って無効にすることである(7.16.11. NOUNPACKプラグマを見よ)。

-funfolding-creation-threshold=n:

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

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

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

-funfolding-use-threshold=n:

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