おもちゃのXMLプロセッサを実装しようとしているプログラムを書いています。現在、プログラムは、文書の構造を記述するイベントのストリーム(SAXと考える)を読み込み、対応するツリーを遅延して構築することになっています。イベントは、以下のデータ型によって定義されるスペースリークのあるレイジーツリー
:
data Event = Open String
| Close
可能な入力は、次のようになり:ツリーに対応する
[Open "a", Open "b", Close, Open "c", Close, Close]
:
a
/\
b c
I希望ツリーを怠け者のように生成したいので、完全な形でメモリに存在する必要はありません いつでも。しかし、私の現在の実装では、スペースリークが発生して、もはや必要でなくなってもすべてのノードが保持されているように見えます。ここでは、コードは次のようになります。
data Event = Open String
| Close
data Tree a = Tree a (Trees a)
type Trees a = [Tree a]
data Node = Node String
trees [] = []
trees (Open x : es) =
let (children, rest) = splitStream es
in (Tree (Node x) (trees children)) : (trees rest)
splitStream es = scan 1 es
scan depth ([email protected](Open {}) : ss) =
let (b, a) = scan (depth+1) ss
in (s:b, a)
scan depth ([email protected] : ss) =
case depth of
1 -> ([], ss)
x -> let (b, a) = scan (depth-1) ss
in (s:b, a)
getChildren = concatMap loop
where
loop (Tree _ cs) = cs
main = print .
length .
getChildren .
trees $
[Open "a"] ++ (concat . replicate 1000000 $ [Open "b", Close]) ++ [Close]
機能trees
はTree Node
のリストにイベントのリストに変換します。 getChildren
は、ルート( "a")のすべての子ノード(ラベル "b")を収集します。これらを数え、結果の数字が印刷されます。
GHC 7.0.4(-O2)で構築されたコンパイル済みプログラムは、ノードカウントを出力する時点までメモリ使用量を増加し続けます。私は、一方で、ほぼ一定のメモリ使用量を期待していました。
"-hd"ヒーププロファイルを見ると、ほとんどのメモリがリストコンストラクタ(:
)によって取得されていることがわかります。 scan
またはtrees
によって生成されたリストの1つが完全に保持されているようです。私はなぜ、なぜ、length . getChildren
が通過するとすぐに子ノードを取り除くべきなのか理解していません。
このようなスペースリークを修正する方法はありますか?
「splitStream」のコードも投稿できますか?それはあまりにも厳しいかもしれません。 – luqui
ところで、私は 'scan'に' Control.Arrow.first'を推奨します: 'let(b、a)= scan(depth + 1)ss(s:b、a)'ではなく 'first (s :)(scan(depth + 1)ss) 'となります。 – luqui
@luqui: 'trees'と' scan'の間にあります。しかし、一本のライナーだから、「splitStream」は見逃しやすい。 –