PR

生存データの状態を知る

 ここまで紹介したオプションを使うことで,時間経過に沿ったメモリー使用量の変化を見ることはできます。しかし,メモリーが使用されている原因を知るための情報は提供されません。領域効率の問題を解決するには,「ヒープ上の生存データの具体的な利用状況」や「データがすぐにGCされるのではなく,ヒープ上に保持されている原因」といった情報を取得する必要があります。このために提供されているのが,-hbオプションと-hrオプションです。

 -hbオプションを使うことで,ヒープ上の生存データの具体的な利用状況がわかります。このオプションでは,データの生存期間中の経歴(Biography)を以下のような状態に分類して出力します(参考リンク1参考リンク2参考リンク3)。

LAG
作成後,最初に利用されるまで待機しているデータ
USE
利用中のデータ
DRAG
最後に利用された後,最後の参照が消えるまでのデータ
INHERENT_USE
参照型や配列といった組み込みのデータ型として使われているデータ
VOID
1回も利用されなかったデータ

 -hbオプションを使ってみましょう。

$ ./Lazy2 +RTS -hb -K100M
500000500000
$ hp2ps -c Lazy2.hp

 この結果から,作成されてから1回も利用されないデータ(VOID)と,作成されてから利用されるまでに時間がかかるデータ(LAG)が多数存在することがわかります。これらは具体的には何なのでしょうか?

 -hbオプションに小文字でデータの状態を与えることで,他の-h<x>オプションの結果から必要な情報を抽出できます。例えば「-hbvoid」と指定した場合にはVOID状態のもの,「-hbvoid,lag」とvoidとlagを列挙したような場合には列挙した状態のいずれかに当てはまるものを抽出できます。「-hbと状態名の間」や「複数列挙時の『,』の前後」に空白を入れてしまうと正しい指定にならないので注意してください。

 では-hb*オプションを使って情報を抽出してみましょう。まず,作成されてから1回も利用されないデータであるVOIDを抽出してみます。

$ ./Lazy2 +RTS -hd -hbvoid -K100M
500000500000
$ hp2ps -c Lazy2.hp

 この結果から,VOIDはBLACKHOLEであることがわかります。BLACKHOLEは,サンク(未評価のクロージャ)の中の値を複数のネイティブ・スレッドが同時に評価しようとした場合,重複評価を防ぐために,先に評価を開始したネイティブ・スレッドが後から来るネイティブ・スレッドをブロックする用途で使われるデータです(参考リンク1参考リンク2参考リンク3)。

 もっとも,「main = print $ sum [0..1000000]」というプログラムの評価には,複数のネイティブ・スレッドが利用されることはありません。このため,サンクに対する処理をブロックするためのBLACKHOLEは,1回も使われずに捨てられるのです。

 BLACKHOLEは暗黙的に作られるデータなので,BLACKHOLE自体を除去することはできません。しかし,サンクを減らすことで,BLACKHOLEの数を減らすことはできます。BLACKHOLEはサンクの数に応じて作られるからです。サンクは値を遅延評価するために作られるものなので,遅延評価を行うコードを正格評価に変えればサンクは減ります。つまり,BLACKHOLEを減らせます。

 次に,作成されてから利用されるまでに時間がかかるデータであるLAGを抽出してみましょう。

$ ./Lazy2 +RTS -hd -hblag -K100M
500000500000
$ hp2ps -c Lazy2.hp

 この結果から,LAGは「<base:Data.List.sat_s39p>」であることがわかりました。