stackを通してのコンパイルにとても時間がかかる条件

ghc 7.10 からみられるようになった現象で,放置しているうちにやや旬を過ぎてしまっている感もある話題なのだが,その後特に改善してるわけでもないのと最近踏まれたのを見たので念のために記しておこうかなと.

結論から言うと,一定レベル以上の最適化付きで,大きな型レベル計算を含んだ型を持つインターフェースを含む場合…となる.このときコンパイルが遅い.厳密には遅いのはコンパイルではないが.

上記ツイートで問題にしてたJinja2サブセットのテンプレートエンジンであるhaijiは,hackageに上げる前のPoC段階で記事にもした.

notogawa.hatenablog.com

なのでここでは概要のみだが,haijiは「レンダリングにどんな名前のどんな型の変数を持つ辞書が必要か」をj2テンプレートの型としてTemplate Haskellで型付けし,条件を満たした辞書をレンダラから渡さないと型検査で弾かれるようにしている*1.ここで利用される辞書はテンプレート変数名の型レベル文字列からその変数の型を引けるような型レベル辞書型を持っており,型レベル文字列とか型レベルリスト上での計算をモリモリ行って辞書同士の結合等を実装している.とにかく,型が型レベル計算で生成され,しかもTemplate Haskellでj2テンプレートから自動生成されているため,型検査に失敗したりすると非常に長いエラーが出たりする.

一方でghc側の事情だが,7.8 から 7.10 時の変化として,通常の最適化(-O)有効時,モジュール外に公開されるもののみならずモジュール内で閉じたものであっても詳細な型情報が.hiファイルに吐かれるようになった*2ようだ.これに伴い.hiファイルのサイズも大きくなっている.

次にstack側の事情になる.stackはコンパイルオプションとしてghc-ddump-hi -ddump-to-fileオプションを渡す.これは,.hiファイルの中身をhuman readableにした.dump-hiファイルを吐かせるオプションだ..stack-workディレクトリ以下を見てもらえば.dump-hiファイルが確認できるだろう.stackはこのオプションを問答無用でghcに渡し,現時点では渡さないという選択肢は取れない.どうも.dump-hiファイルの情報を使っているとのこと.

これらの事情が絡み合い,「ghc 7.10 以降stackを通してコンパイルすると異常に大きな.ddump-hiファイルが吐かれる」状況が発生する.

ファイルサイズも問題無いとは言えないのだが,それ以上にどうも型レベル計算で大きく構成された型に対してはhuman readable dumpの生成か整形あたりが特に遅いようで,これがstackでのコンパイル時間にまるまる乗ってしまう.当然だが,stackを使わない(=dump系オプションを渡さない)ならば遅くはならない.とはいえ,そういうわけにもねぇ.

*1:そのためテンプレートは実行時ではなくコンパイル時にロードされる.

*2:モジュールを跨いだ最適化に使える情報を増やすためか