haskellプログラムがメモリを食いまくって落ちていたのはghcに-O0を指定していたからだった

テストコードでも-O0はやめよう.

yesod-testがメモリを食いまくる

今,私はYesodでWebアプリケーションを書いていて,まだseleniumを導入してなかったので,yesod-testで大きいサイズ(100MBぐらい)のファイルを大量に投稿するというテストを書く必要がありました.

しかし,そのテストを実行して,testプログラムがファイルを投稿すると,メモリを10GB以上余裕で消費して,20GB取ってるswapすら超越し,OOM killerが発動したり,segvしてしまい困っていました.

ソースコードを追ってみると,yesod-testはファイルの中身をByteStringで保持しているようでした.

しかし,確かに大型のファイルを投稿するのですが,ちゃんとリソースを開放していればファイルサイズ程度しかメモリを消費しないようになっているはずです.

スリープを挟んでみたり,performGCを挟んでみましたが,ダメでした.

プロファイリングを有効にしてビルド

とりあえず測定してみるべきだと思ったので,stackを使った測定の方法を調べました.

普通にstack test --profileを実行すればprofファイルは出力されるようです.

profiteur

profファイルはどうもテキストファイルで見てもよくわかりませんでした.

ビジュアライズできるツールは無いかなあと思って調べていたら,jaspervdj大先生(hakyllstylish-haskellの作者様)が作っていました.

jaspervdj/profiteur: Visualiser for Haskell (GHC) prof files

profiteurにprofファイルを入力したら,htmlを出力してくれます.

最適化を有効にする

測定結果を見ていたら,CAFをよく見るので,スペースリークしてるのかなあ,正格評価と言えば,ghcの最適化を切ってたなと思い出しました.

ghc-options: -Wall -fno-warn-orphans -fno-hpc -O0

テストコードはコンパイルしてから実行するのは1回だけだから,-O0で問題ないと思ってコンパイル速度重視で付けていました.

試しにこれを切ってみたところ,正常にリソース開放が行われ,メモリ使用量はファイルサイズぐらいに収まりました.

travis ciの設定にも

script:
  - stack --jobs 2 --no-terminal --fast test

などと書かれていたので,--fastオプションを切ったら,travis ciのメモリが少ない環境でもテストが通りました.

-O0はテストコードでもよろしくないということがわかりました.-O2もメタプログラミングのコードを最適化しようとすると危険ですが,-O0も危険です.