尽管总内存使用量只有 22Mb,但 Haskell 线程堆溢出?

Posted

技术标签:

【中文标题】尽管总内存使用量只有 22Mb,但 Haskell 线程堆溢出?【英文标题】:Haskell threads heap overflow despite only 22Mb total memory usage? 【发布时间】:2015-10-16 22:51:14 【问题描述】:

我正在尝试并行化光线追踪器。这意味着我有一个很长的小计算列表。 vanilla 程序在特定场景上运行时间为 67.98 秒,总内存使用量为 13 MB,工作效率为 99.2%。

在我的第一次尝试中,我使用了缓冲区大小为 50 的并行策略 parBuffer。我选择了 parBuffer,因为它只在消耗火花时才通过列表,并且不会强制列表的脊椎比如parList,由于列表很长,会占用大量内存。使用-N2,它的运行时间为 100.46 秒,总内存使用量为 14 MB,工作效率为 97.8%。火花信息为:SPARKS: 480000 (476469 converted, 0 overflowed, 0 dud, 161 GC'd, 3370 fizzled)

fizzled sparks比例大说明sparks的粒度太小,所以接下来我尝试使用parListChunk的策略,将列表拆分成块,并为每个块创建一个spark。我使用0.25 * imageWidth 的块大小得到了最好的结果。该程序运行时间为 93.43 秒,总内存使用量为 236 MB,工作效率为 97.3%。火花信息为:SPARKS: 2400 (2400 converted, 0 overflowed, 0 dud, 0 GC'd, 0 fizzled)。我相信更大的内存使用是因为parListChunk 强制列表的脊椎。

然后我尝试编写自己的策略,将列表懒惰地分成块,然后将块传递给parBuffer 并将结果连接起来。

 concat $ withStrategy (parBuffer 40 rdeepseq) (chunksOf 100 (map colorPixel pixels))

运行时间为 95.99 秒,总内存使用量为 22MB,工作效率为 98.8%。从某种意义上说,这是成功的,所有的火花都在转换并且内存使用量要低得多,但是速度并没有提高。这是部分事件日志配置文件的图像。

正如您所见,由于堆溢出,线程正在停止。我尝试添加+RTS -M1G,它将默认堆大小一直增加到 1Gb。结果没有改变。我读到如果 Haskell 主线程的堆栈溢出,它将使用堆中的内存,因此我也尝试使用 +RTS -M1G -K1G 增加默认堆栈大小,但这也没有影响。

还有什么我可以尝试的吗?如果需要,我可以发布有关内存使用或事件日志的更详细的分析信息,但我没有将其全部包含在内,因为它包含很多信息,而且我认为没有必要包含所有这些信息。

编辑:我正在阅读Haskell RTS multicore support,它谈到每个核心都有一个HEC(Haskell 执行上下文)。除其他外,每个 HEC 都包含一个分配区域(它是单个共享堆的一部分)。每当任何 HEC 的分配区域用尽时,都必须执行垃圾收集。似乎是一个RTS option 来控制它,-A。我试过 -A32M 但没有发现任何区别。

编辑2: Here is a link to a github repo dedicated to this question。我已将分析结果包含在分析文件夹中。

EDIT3:这是相关的代码:

render :: [([(Float,Float)],[(Float,Float)])] -> World -> [Color]
render grids world = cs where 
  ps = [ (i,j) | j <- reverse [0..wImgHt world - 1] , i <- [0..wImgWd world - 1] ]
  cs = map (colorPixel world) (zip ps grids)
  --cs = withStrategy (parListChunk (round (wImgWd world)) rdeepseq) (map (colorPixel world) (zip ps grids))
  --cs = withStrategy (parBuffer 16 rdeepseq) (map (colorPixel world) (zip ps grids))
  --cs = concat $ withStrategy (parBuffer 40 rdeepseq) (chunksOf 100 (map (colorPixel world) (zip ps grids)))

网格是由 colorPixel 预先计算和使用的随机浮点数。colorPixel 的类型是:

 colorPixel :: World -> ((Float,Float),([(Float,Float)],[(Float,Float)])) -> Color

【问题讨论】:

您能否提供您尝试concat $ withStrategy … 的确切提交?我无法在6008010 中重现此行为,这是最接近您的编辑的提交。 我做了一个专门的回购,所以我不会不小心把它弄乱了。我还包括了所有的分析信息。 @dfeuer 当我说定义我自己的策略时,我并不是说Strategy。应该选一个更好的词。此外,parListChunkparBuffer 也会出现堆溢出问题。 【参考方案1】:

不是解决问题的方法,而是提示原因:

Haskell 似乎在内存重用方面非常保守,当解释器看到回收内存块的潜力时,它就会去做。您的问题描述符合此处描述的次要 GC 行为(底部) https://wiki.haskell.org/GHC/Memory_Management.

新数据分配在 512kb 的“nursery”中。一旦用尽,“小 GC”发生 - 它扫描 Nursery 并释放未使用的值。

因此,如果您将数据分割成更小的块,您可以让引擎更早地进行清理 - GC 启动。

【讨论】:

以上是关于尽管总内存使用量只有 22Mb,但 Haskell 线程堆溢出?的主要内容,如果未能解决你的问题,请参考以下文章

尽管有大量可用内存,但 malloc() 失败

Unity 分析我的脚本的内存使用情况

在 Haskell 中优化基数排序

了解此 Haskell 程序的内存使用情况

装了4条8G共32G内存,但是在photoshop首选项--性能—可用内存中显示只有3255MB,无论怎么调都只有3G多,

如何避免Haskell空间泄漏? [关闭]