PR

データがGCされない原因を知る

 データがGCされずにヒープに残る原因は,-hrオプションで調べられます。このオプションを使うことで,データをある程度の期間保持し,その結果データをヒープ上に残す原因となった「システムのスタック」または「サンクに対応するコスト集約点」を,「データの保持者」(retainer)として出力します。結果は,<実行コマンド名>.hpと<実行コマンド>.profというファイルに保存されます。データ構成子は保持者にはならない点に注意してください。他の保持者から間接参照されるデータは保持者とはみなされません(参考リンク)。

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

 グラフで示されているデータの保持者は,mainとシステムのスタックであるSYSTEMだけです。mainが保持するデータにはコスト集約点があるのですが,この場合はすべての項目名をグラフに表示しきれないため,グラフには完全な情報は表示されていません。代わりにグラフの項目に番号が振られているので,<実行コマンド名>.profの対応する番号を参照することで,データに対する保持者の一覧を知ることができます。この場合には,Lazy2.profに以下のような情報が出力されています。

Retainer Profiling: 0, at 0.031201 seconds
        Average number of visits per object = 1.500074
Retainer Profiling: 1, at 0.187202 seconds
        Average number of visits per object = 1.500236

Retainer sets created during profiling:
SET 2 = {<MAIN.SYSTEM>}
SET 3 = {<Main.main,Main.CAF:main>}
SET 9 = {<GHC.IO.Handle.FD.CAF>}
SET 34 = {<GHC.Conc.Sync.CAF>, <MAIN.SYSTEM>}
SET 38 = {<GHC.IO.Handle.FD.CAF>, <GHC.Conc.Sync.CAF>, <MAIN.SYSTEM>}
SET 39 = {<:Main.CAF:main>}
SET 40 = {<:Main.CAF:main>, <MAIN.SYSTEM>}

 グラフでのmainの番号である「3」と,*.profファイルの「SET 3」を比べてみると,グラフに示されていたのは,Mainモジュールのmain関数とMainモジュールのCAF:mainの二つの保持者によって保持されていたデータであることがわかります。

 この結果は,第9回の説明とは異なっています。第9回では,sum関数の内部で使われているfoldl関数が大量のデータをメモリー中に保持させてしまう犯人であることを指摘しました。しかし,グラフやLazy2.profにデータの保持者として示されているのはmainとCAF:mainだけであり,sumやfoldlの名前はどこにもありません。

 保持者がこのような形に提示されるのは,sum関数やfoldl関数を指すコスト集約点が設定されていないからです。プロファイル結果が煩雑になるのを防ぐために,GHCやHaskell Platformに付属するライブラリでは,通常の関数や個別のCAFといった具体的なコスト集約点は設定されていません。個別のコスト集約点が設定されていないため,Lazy2.profでは保持者の多くがモジュール名で示されています。

 -hrオプションは,コスト集約点を保持者として提示するため,コスト集約点が設けられていないsum関数やfoldl関数を保持者とみなすことはできません。そこでsum関数を呼び出すmain関数やCAF:mainだけを保持者とみなして出力しているのです。

 -hrオプションでは実際の保持者を特定できない場合には,前回説明したプロファイル機能と同様に,コスト集約点を増やす必要があります。例えばこのプログラムでは,sum関数やfoldl関数の定義をMainモジュールに移し,-auto-allオプションを付けてプログラムをビルドすることで,sumやfoldlが保持者として表示されるようになります。

import Prelude hiding (foldl, sum)

main = print $ sum [0..1000000]

sum              :: (Num a) => [a] -> a
sum              =  foldl (+) 0


foldl        :: (a -> b -> a) -> a -> [b] -> a
foldl f z0 xs0 = lgo z0 xs0
             where
                lgo z []     =  z
                lgo z (x:xs) = lgo (f z x) xs

$ ghc  Lazy2.hs -rtsopts -prof -auto-all -caf-all
[1 of 1] Compiling Main             ( Lazy2.hs, Lazy2.o )
Linking Lazy2 ...
$ ./Lazy2 +RTS -hr -K100M
500000500000
$ hp2ps -c Lazy2.hp
~ 略 ~

Retainer sets created during profiling:
SET 2 = {<MAIN.SYSTEM>}
SET 9 = {<MAIN.SYSTEM>, <Main.foldl,Main.sum,Main.main,Main.CAF:main>}
SET 10 = {<Main.foldl,Main.sum,Main.main,Main.CAF:main>}
SET 11 = {<GHC.IO.Handle.FD.CAF>}
SET 36 = {<GHC.Conc.Sync.CAF>, <MAIN.SYSTEM>}
SET 40 = {<GHC.IO.Handle.FD.CAF>, <GHC.Conc.Sync.CAF>, <MAIN.SYSTEM>}
SET 41 = {<:Main.CAF:main>}
SET 42 = {<:Main.CAF:main>, <MAIN.SYSTEM>}