Haskell: ST中存在内存泄漏/垃圾回收未生效?

6

我在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的向量(未被使用,未在ST外返回或引用)下调用runST 20次进行计算。最大驻留空间看起来很好,基本上只有我的向量加上几MB的其他东西。但是总内存使用量表明我同时拥有四个向量副本。这与向量的大小完全成比例,对于256MB,我们可以得到预期的1030MB。

使用1GB的向量将耗尽内存(4x1GB + 超额空间>32位)。我不理解为什么RTS会保留看起来未使用、未引用的内存,而不是在分配失败之前进行垃圾回收。

使用+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 profile 基本上只是说我们分配了 128MB:

http://imageshack.us/a/img69/7765/45q8.png

我尝试在一个更简单的程序中复制这种行为,但即使使用 ST、包含 Vector 的 Reader,相同的 monad/program 结构等,简单的测试程序也没有显示出这种情况。当我简化我的大程序并删除显然完全不相关的代码时,这种行为最终也会停止。

问题:

  • 我真的把这个向量保存了 20 次中的 4 次吗?
  • 如果是的话,我该如何判断,因为 +RTS -Hymaximum residency 声称我没有,并且我该怎么做才能阻止这种行为?
  • 如果不是的话,为什么 Haskell 没有 GC 它并耗尽地址空间 / 内存,我该怎么做才能阻止这种行为?

谢谢!


2
通常使用的内存是最大驻留内存的两倍或更多,这取决于分配和收集模式。因此,518MB的总内存使用量本身并不令人担忧。例如,尝试告诉 GHC 只能使用有限的内存,$ ./foo +RTS -M256M,以强制它更早地进行垃圾回收。但是,“在 runST 之外没有任何引用保留”可能是不正确的,如果是这种情况,需要查看代码以确定是否存在泄漏。 - Daniel Fischer
@DanielFischer,518MB是大约4倍,但是1GB向量的内存不足崩溃难道不表明GHC 无法收集内存吗?+RTS -M256M失败并显示'Heap exhausted'。向量被创建后放置在Reader环境中,仅此而已。没有其他操作,不确定如何在离开ST / Reader之后避免泄漏任何引用。正如我所说,我无法在更简单的程序中重现此问题。它似乎相当随机。 - NBFGRTW
好的,如果分配模式合适/不合适,则可能会发生4×。内存不足崩溃可能表明 GHC 不知道现在应该进行收集,但是考虑到“-M256M”导致“堆耗尽”,看起来更像是某个东西仍然引用了该对象。 - Daniel Fischer
2
你的 runST 计算的返回类型是什么?可能存在一些隐藏的惰性。尝试使用 deepseq 或者使用 ghc-heap-view 进行调查。 - Joachim Breitner
“当我多次运行我的ST计算时”的分配模式可能是它。但无论如何,没有代码,我们只能盲目猜测。 - Daniel Fischer
显示剩余5条评论
1个回答

2
我怀疑这是GHC和/或RTS中的一个bug。
首先,我确信没有实际的空间泄漏或其他类似问题。
原因:
- 向量在任何地方都没有使用过。不读取、不写入、不引用。一旦runST完成,它应该被收集。即使ST计算返回一个单独的Int并立即打印出来以评估它,内存问题仍然存在。那些数据没有被引用。 - RTS提供的每种分析模式都强烈表示,我从未实际上有超过单个向量的内存分配/引用。每个统计数据和漂亮的图表都表明了这一点。
现在,有趣的部分来了。如果我在每次运行我的函数后手动强制执行GC,问题就完全解决了。
因此,我们有一个情况,运行时拥有GB级别的内存,(可证明!)可以通过GC回收,甚至根据自己的统计数据,没有任何人再持有这些内存。当其内存池耗尽时,运行时不进行回收,而是请求操作系统获取更多内存。即使最终失败,运行时仍然不会回收(可以证明可以回收GB级别的内存),而是选择用内存不足错误终止程序。
我不是Haskell、GHC或GC的专家。但是,这对我来说看起来非常糟糕。我会将其报告为一个bug。

你有没有报告过这个问题? - jberryman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接