BFS 实现中的 Haskell 空间泄漏
连续几天我一直在努力解决 Haskell 空间泄漏问题(自然是堆栈溢出类型)。这很令人沮丧,因为我试图直接从 CLR 模仿 BFS 算法,这不是自然递归的。注意:我启用了 BangPatterns,并在每个可能的位置前面放了一个爆炸,试图分支和限制这个问题,但没有效果。我以前曾与空间泄漏作过斗争,在这一次我不愿意放弃并寻求帮助,但在这一点上我陷入了困境。我喜欢用 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
I have been banging my head against a Haskell space leak (of the stack overflow kind, naturally) for a few straight days. It's frustrating because I'm attempting to mimic the BFS algorithm straight from CLR, which is not naturally recursive. NB: I have enabled BangPatterns and I have put a bang in front of every possible place where one can go, in an attempt to branch-and-bound this problem, with no effect. I have battled through space leaks before, and I am loth to give up and cry for help on this one, but at this point I'm stuck. I love coding in Haskell, and I understand the Zen of functional programming pretty well, but debugging space leaks is about as much fun as rolling around on a floor full of thumbtacks.
That said, my trouble appears to be a space leak of the typical "accumulator" kind. The stack evidently builds up around calls to bfs' in the code below. Any space-leak protips much appreciated.
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
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
这看起来更容易理解。 (不过,您仍然可以将代码缩小 1/2。)
现在,空间泄漏的本质变得显而易见。也就是说,永远不会评估的一件事是深度。它将堆积成一个大表达式
1+1+...
。您可以删除所有刘海图案并添加一个刘海图案以消除空间泄漏。
(更多代码提示:您可以用一个简单的列表替换
IS.IntSet
。队列最好按照以下方式解构和重建)
That looks much easier to understand. (You can still shrink the code by 1/2, though.)
Now, the nature of the space leak becomes apparent. Namely, the one thing that is never evaluated is the depth. It will pile up to a big expression
1+1+...
. You can remove all the bang patterns and add a single one atto get rid of the space leak.
(Further code tips: You can replace the
IS.IntSet
by a simple list. The queue is best deconstructed and reconstructed along the lines of)
首先,如果您可以提供一些简单的测试用例(以代码的形式)来演示这个东西堆栈如何溢出,那将会非常有帮助。
如果没有它,我个人只能推测其原因。
作为猜测:IS.fold 是否足够严格?好吧,例如下面最简单的代码堆栈溢出(GHC with -O2):
此代码的溢出问题可以通过 hackafixed(有更好的方法吗?),如下所示:
也许您想查看
IS。也将
折叠到您的代码中。First of all, if would be very helpful if you could provide some simple test case (in the form of code) which demonstrates how this thing stack overflows.
Without it I, personally, can only speculate on the subject of reason for that.
As a speculation: is
IS.fold
strict enough? Well, for example the following simplest code stack overflows as well (GHC with -O2):The overflow problem with this code can be hackafixed (is there a better way?) like that:
Maybe you want to look at
IS.fold
in your code as well.