【问题标题】:Haskell space leak in implementation of BFSBFS 实现中的 Haskell 空间泄漏
【发布时间】:2011-04-16 07:37:26
【问题描述】:

连续几天我一直在努力解决 Haskell 空间泄漏(自然是堆栈溢出类型)。这令人沮丧,因为我试图直接从 CLR 模仿 BFS 算法,这不是自然递归的。注意:我已经启用了 BangPatterns,并且我在每个可以去的地方都放了一个 bang,试图对这个问题进行分支和绑定,但没有任何效果。我以前曾与空间泄漏作斗争,我不愿意放弃并为此而哭泣寻求帮助,但此时我被困住了。我喜欢在 Haskell 中编码,并且非常了解函数式编程的禅宗,但是调试空间泄漏就像在满是图钉的地板上滚来滚去一样有趣。

也就是说,我的问题似乎是典型的“累加器”类型的空间泄漏。在下面的代码中,堆栈显然是围绕对 bfs' 的调用而建立的。非常感谢任何空间泄漏提示。

import qualified Data.Map as M
import qualified Data.IntSet as IS
import qualified Data.Sequence as S
import qualified Data.List as DL

data BfsColor = White | Gray | Black deriving Show
data Node =
Node {
  neighbors :: !IS.IntSet,
  color     :: !BfsColor,
  depth     :: !Int
   }

type NodeID = Int
type NodeQueue = S.Seq NodeID
type Graph = M.Map NodeID Node

bfs :: Graph -> NodeID -> Graph
bfs graph start_node =
  bfs' (S.singleton start_node) graph

bfs' :: NodeQueue -> Graph -> Graph
bfs' !queue !graph
  | S.null queue = graph
  | otherwise =
  let (u,q1) = pop_left queue
      Node children _ n = graph M.! u
      (g2,q2) = IS.fold (enqueue_child_at_depth $ n+1) (graph,q1) children
      g3 = set_color u Black g2
  in bfs' q2 g3

enqueue_child_at_depth :: Int -> NodeID -> (Graph, NodeQueue)
                                        -> (Graph, NodeQueue)
enqueue_child_at_depth depth child (graph,!queue)  =
  case get_color child graph of
    White     -> (set_color child Gray $ set_depth child depth graph,
                   queue S.|> child)
    otherwise -> (graph,queue)

pop_left :: NodeQueue -> (NodeID, NodeQueue)
pop_left queue =
  let (a,b) = S.splitAt 1 queue
  in (a `S.index` 0, b)

set_color :: NodeID -> BfsColor -> Graph -> Graph
set_color node_id c graph =
  M.adjust (\node -> node{color=c}) node_id graph

get_color :: NodeID -> Graph -> BfsColor
get_color node_id graph = color $ graph M.! node_id

set_depth :: NodeID -> Int -> Graph -> Graph
set_depth node_id d graph =
  M.adjust (\node -> node{depth=d}) node_id graph

【问题讨论】:

  • 您的代码很难阅读和理解。我建议稍微考虑一下图形抽象,引入type NodeId = Intneighbors :: NodeId -> Graph -> [NodeId] 等函数。要修复空间泄漏,您的代码必须尽可能简单。
  • 要记住的一点是,过度使用严格的注释可能会导致内存峰值而不是改善它们。如果你有一个巨大的结构 - 如果你强制它的所有元素,它将会是巨大的。
  • Cormen Leiserson Rivest。但我现在可以看到,CLR 在这里更经常地有你的定义。因此,我删除了标签。
  • 你错过了斯坦。我一直听说它被称为 CLRS
  • 现在我约会了。从第 2 版开始添加 Stein。我没有注意到

标签: haskell memory-leaks space breadth-first-search


【解决方案1】:

这看起来更容易理解。 (不过,您仍然可以将代码缩小 1/2。)

现在,空间泄漏的性质变得明显了。也就是说,永远不会评估的一件事是深度。它将堆积成一个大表达式1+1+...。您可以删除所有的刘海模式并在

enqueue_child_at_depth !depth child (graph,queue)

消除空间泄漏。

(进一步的代码提示:可以将IS.IntSet替换为一个简单的列表。队列最好按照以下思路解构和重构

go depth qs graph = case viewl qs of
    EmptyL  -> graph
    q :< qs ->
        let
            qs' = (qs ><) . Seq.fromList
                . filter (\q -> isWhite q graph)
                . neighbors q $ graph
        in ...

)

【讨论】:

  • 再次感谢海因里希。我将进一步研究。 (作为 Haskell 新手,如果您想输入其中的几个,我会很感激阅读您可能拥有的任何收缩提示)。
  • 更新:没关系,我自己的工作很有趣!问候,c
【解决方案2】:

首先,如果你能提供一些简单的测试用例(以代码的形式)来演示这个东西是如何堆栈溢出的,那将会非常有帮助。 没有它,我个人只能推测原因。

推测:IS.fold 够严格吗?好吧,例如以下最简单的代码堆栈也会溢出(带有-O2的GHC):

{-# LANGUAGE BangPatterns #-}
import qualified Data.IntSet as IS

test s = IS.fold it 1 s
    where it !e !s = s+e

main = print $ test (IS.fromList [1..1000000])

这段代码的溢出问题可以像这样被hackafix(有没有更好的方法?):

test s = foldl' it 1 (IS.toList s)
    where it !e !s = s+e

也许你也想在你的代码中查看IS.fold

【讨论】:

  • 谢谢埃德卡。我想到了这一点并修复它并没有帮助,所以我保留了代码(尽管我注意到在 haskell.org 上,有一些功能请求很快就会向 Maps 和 IntSets 添加严格折叠,所以我们并不孤单)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多