Haskell:从 ST / GC 泄漏内存不收集?
Posted
技术标签:
【中文标题】Haskell:从 ST / GC 泄漏内存不收集?【英文标题】:Haskell: Leaking memory from ST / GC not collecting? 【发布时间】:2013-08-16 18:49:21 【问题描述】:我在 ST 内部有一个计算,它通过 Data.Vector.Unboxed.Mutable 分配内存。该向量永远不会被读取或写入,也不会在 runST 之外保留对它的任何引用(据我所知)。我遇到的问题是,当我多次运行 ST 计算时,有时我似乎保留了向量的内存。
分配统计:
5,435,386,768 bytes allocated in the heap
5,313,968 bytes copied during GC
134,364,780 bytes maximum residency (14 sample(s))
3,160,340 bytes maximum slop
518 MB total memory in use (0 MB lost due to fragmentation)
在这里,我使用不同的计算值和 128MB 向量调用 runST 20x(同样 - 未使用,未在 ST 之外返回或引用)。最大驻留看起来不错,基本上只是我的向量加上几 MB 的其他东西。但是总内存使用表明我有四个同时处于活动状态的向量副本。这与向量的大小完美匹配,对于 256MB,我们得到 1030MB 的预期值。
使用 1GB 向量会耗尽内存(4x1GB + 开销 > 32 位)。我不明白为什么 RTS 保留看似未使用的、未引用的内存,而不是仅仅 GC 处理它,至少在分配会失败的时候。
使用 +RTS -S 运行会显示以下内容:
Alloc Copied Live GC GC TOT TOT Page Flts
bytes bytes bytes user elap user elap
134940616 13056 134353540 0.00 0.00 0.09 0.19 0 0 (Gen: 1)
583416 6756 134347504 0.00 0.00 0.09 0.19 0 0 (Gen: 0)
518020 17396 134349640 0.00 0.00 0.09 0.19 0 0 (Gen: 1)
521104 13032 134359988 0.00 0.00 0.09 0.19 0 0 (Gen: 0)
520972 1344 134360752 0.00 0.00 0.09 0.19 0 0 (Gen: 0)
521100 828 134360684 0.00 0.00 0.10 0.19 0 0 (Gen: 0)
520812 592 134360528 0.00 0.00 0.10 0.19 0 0 (Gen: 0)
520936 1344 134361324 0.00 0.00 0.10 0.19 0 0 (Gen: 0)
520788 1480 134361476 0.00 0.00 0.10 0.20 0 0 (Gen: 0)
134438548 5964 268673908 0.00 0.00 0.19 0.38 0 0 (Gen: 0)
586300 3084 268667168 0.00 0.00 0.19 0.38 0 0 (Gen: 0)
517840 952 268666340 0.00 0.00 0.19 0.38 0 0 (Gen: 0)
520920 544 268666164 0.00 0.00 0.19 0.38 0 0 (Gen: 0)
520780 428 268666048 0.00 0.00 0.19 0.38 0 0 (Gen: 0)
520820 2908 268668524 0.00 0.00 0.19 0.38 0 0 (Gen: 0)
520732 1788 268668636 0.00 0.00 0.19 0.39 0 0 (Gen: 0)
521076 564 268668492 0.00 0.00 0.19 0.39 0 0 (Gen: 0)
520532 712 268668640 0.00 0.00 0.19 0.39 0 0 (Gen: 0)
520764 956 268668884 0.00 0.00 0.19 0.39 0 0 (Gen: 0)
520816 420 268668348 0.00 0.00 0.20 0.39 0 0 (Gen: 0)
520948 1332 268669260 0.00 0.00 0.20 0.39 0 0 (Gen: 0)
520784 616 268668544 0.00 0.00 0.20 0.39 0 0 (Gen: 0)
521416 836 268668764 0.00 0.00 0.20 0.39 0 0 (Gen: 0)
520488 1240 268669168 0.00 0.00 0.20 0.40 0 0 (Gen: 0)
520824 1608 268669536 0.00 0.00 0.20 0.40 0 0 (Gen: 0)
520688 1276 268669204 0.00 0.00 0.20 0.40 0 0 (Gen: 0)
520252 1332 268669260 0.00 0.00 0.20 0.40 0 0 (Gen: 0)
520672 1000 268668928 0.00 0.00 0.20 0.40 0 0 (Gen: 0)
134553500 5640 402973292 0.00 0.00 0.29 0.58 0 0 (Gen: 0)
586776 2644 402966160 0.00 0.00 0.29 0.58 0 0 (Gen: 0)
518064 26784 134342772 0.00 0.00 0.29 0.58 0 0 (Gen: 1)
520828 3120 134343528 0.00 0.00 0.29 0.59 0 0 (Gen: 0)
521108 756 134342668 0.00 0.00 0.30 0.59 0 0 (Gen: 0)
在这里,我们的“活动字节”似乎超过了 ~128MB。
+RTS -hy
配置文件基本上只是说我们分配了 128MB:
http://imageshack.us/a/img69/7765/45q8.png
我尝试在一个更简单的程序中重现此行为,但即使使用 ST、包含向量的阅读器、相同的单子/程序结构等复制确切的设置,简单的测试程序也不会显示这一点。简化我的大程序时,当删除明显完全不相关的代码时,该行为最终也会停止。
问:
我真的将这个向量保持在 20 次中的 4 次左右吗? 如果是,我该如何判断,因为+RTS -Hy
和 maximum residency
声称我不是,我能做些什么来阻止这种行为?
如果不是,为什么 Haskell 不对其进行 GC 并耗尽地址空间/内存,我该怎么做才能阻止这种行为?
谢谢!
【问题讨论】:
使用的内存通常是最大驻留的两倍或更多,取决于分配和收集模式。因此,使用的 518MB 总内存本身并不令人担忧。尝试告诉 GHC 只有这么多内存可以使用,例如$ ./foo +RTS -M256M
,以强制它提前收集。但是“在 runST 之外也没有保留对它的任何引用”可能是不真实的,你确实可能有泄漏。如果是这种情况,则需要查看代码。
@DanielFischer 518MB 大约是 4 倍。 1GB 向量的内存不足崩溃是否表明 GHC 无法 收集内存? +RTS -M256M 因“堆耗尽”而失败。向量被创建,放在 Reader 环境中,就是这样。没有别的了,不知道在离开 ST/Reader 后我还能做些什么来避免泄露任何引用。就像我说的,我无法在更简单的程序中重现这个问题。这似乎相当随机。
好吧,如果分配模式正确/错误,可能会发生 4 次。内存不足崩溃可能表明 GHC 不知道它现在应该收集,但鉴于 -M256M
导致“堆耗尽”,它看起来更像是对野兽的引用。
@DanielFischer 一个向量分配一个 128MB 的块,我怎么能有比这更好的模式?我只使用复制构建向量并将其放入阅读器的记录中。除了这些地方,删除该字段不会导致编译器错误,所以我有信心它没有被使用。离开 runReaderT/runST 后如何保留参考?我不确定如何在 Haskell 中诊断此类问题。
runST
计算的返回类型是什么?可能有一些隐藏的懒惰。尝试deepseq
或使用ghc-heap-view
进行调查。
【参考方案1】:
我怀疑这是 GHC 和/或 RTS 中的错误。
首先,我确信没有实际的空间泄漏或类似情况。
原因:
向量永远不会在任何地方使用。不读,不写,不参考。一旦 runST 完成,就应该收集它。即使 ST 计算返回单个 Int 并立即打印出来以对其进行评估,内存问题仍然存在。没有引用该数据。 RTS 提供的每一种分析模式都强烈一致认为我实际上分配/引用的内存永远不会超过一个向量的价值。每个统计数据和漂亮的图表都说明了这一点。现在,有趣的地方来了。如果我在每次运行我的函数后通过调用 System.Mem.performGC
手动强制 GC,问题就会完全消失。
所以我们有一个案例,运行时有 GB 的内存(显然!)可以被 GC 回收,甚至根据它自己的统计数据,任何人都不再持有。当内存池用完时,运行时不会收集,而是要求操作系统提供更多内存。即使最终失败,运行时仍然不会收集(这显然会回收 GB 的内存),而是选择因内存不足错误而终止程序。
我不是 Haskell、GHC 或 GC 方面的专家。但这在我看来确实很糟糕。我会将此报告为错误。
【讨论】:
你有没有报告过这个?以上是关于Haskell:从 ST / GC 泄漏内存不收集?的主要内容,如果未能解决你的问题,请参考以下文章