Haskell Data.Vector,巨大的内存泄漏

Haskell Data.Vector, huge memory leak

我正在尝试使用 haskell 和 SDL1.2 绑定制作一个基本的 2D 引擎(为了好玩,我只是在学习)。 理想情况下,世界是程序生成的,一块一块地,允许自由探索。

现在我的块由 200*200 个图块组成,我使用以下类型表示:

Mat [Tile] = Vec.Vector (Vec.Vector [Tile])

以及这些函数:

fromMat :: [[a]] ->  Mat a
fromMat xs = Vec.fromList [Vec.fromList xs' | xs' <- xs]

(§) :: Mat a -> (Int, Int) -> a
v § (r, c) = (v Vec.! r) Vec.! c

我正在使用图块的循环列表以允许精灵动画,以及稍后的动态行为。

游戏循环的每一帧,程序读取与当前摄像机位置相关的向量部分,显示相应的图块和return一个新向量,其中每个循环列表都已被替换靠它的尾巴。

这是负责此操作的代码:

applyTileMat :: Chunk -> SDL.Surface -> SDL.Surface -> IO Chunk
applyTileMat ch src dest = 
  let m = chLand $! ch
      (x,y) = chPos ch
      wid = Vec.length (m Vec.! 0) - 1
      hei = (Vec.length m) - 1
      (canW,canH) = canvasSize ch in

  do sequence $ [ applyTile (head (m § (i,j))) (32*(j-x), 32*(i-y)) src dest | i <- [y..(y+canH)], j <- [x..(x+canW)]]
     m' <-sequence $ [sequence [(return $! tail (m § (i,j))) | j <- [0..wid]] | i <- [0..hei]] --weird :P
     return ch { chLand = fromMat m' }

第一个序列做显示部分,第二个return是新向量m'。

起初我使用下面的理解来得到 m'

let !m' = [id $! [(tail $! (m § (i,j))) | j <- [0..wid]] | i <- [0..hei]]

但这样做会导致内存使用量不断增加。我认为这与延迟评估有关,阻止了数据被正确地垃圾收集,但我真的不明白为什么。

在这种特殊情况下,它并不重要,因为我必须查看整个矢量。但是我不知道如果我只想 "update" 每帧我的块的一部分,我该怎么办,从而制作一个新的块,只包含前一个块的部分数据。

我可能没有按照预期的方式使用 Data.Vector,但它是我在 O(n) 随机访问中发现的最简单的数据结构。

完整代码在那里: https://github.com/eniac314/wizzard/blob/master/tiler.hs

问题确实是向量在元素中是惰性的。首先,让我们看看为什么您的示例不起作用。

let !m' = [id $! [(tail $! (m § (i,j))) | j <- [0..wid]] | i <- [0..hei]]

!m 中的刘海图案作用不大。 ! 所做的只是确保变量是构造函数或 lambda,而不是函数应用程序。这里 !m 可以被识别为 [](:) 而无需评估任何元素。同样,įd $!-s 不强制内部列表的任何实际元素。

return ch { chLand = fromMat m' }

fromMat 是下一个罪魁祸首。 fromMat 不强制内部向量,也不强制元素。因此,对旧向量的引用会无限期地保留在 thunk 中。

通常正确的解决方案是导入 Control.DeepSeq,并使用 force$!! 来完全评估向量。不幸的是,由于循环列表,我们不能在这里这样做(试图强制一个结果导致无限循环)。

我们真正需要的是一个将向量的所有元素转化为弱头部范式的函数:

whnfElements :: Vector a -> Vector a
whnfElements v = V.foldl' (flip seq) () v `seq` v

我们可以使用它来为向量定义一个严格的map

vmap' :: (a -> b) -> Vector a -> Vector b
vmap' f = whnfElements . V.map f

现在更新为:

update :: Mat [Tile] -> Mat [Tile]
update = (vmap' . vmap') tail