尽管只有22Mb的总内存使用量,Haskell线程还是会发生堆溢出?


114

我正在尝试并行化光线追踪器。这意味着我有很长的小型计算清单。Vanilla程序在特定场景上运行需要67.98秒,总内存使用量为13 MB,生产率为99.2%。

在我的第一次尝试中,我使用parBuffer了缓冲区大小为50 的并行策略。我之所以选择parBuffer它,是因为它仅在消耗火花时才通过列表,并且不会像这样强行parList使用列表的主干,因为它将消耗大量内存。因为列表很长。使用-N2,它的运行时间为100.46秒,总内存使用量为14 MB,生产率为97.8%。火花信息是:SPARKS: 480000 (476469 converted, 0 overflowed, 0 dud, 161 GC'd, 3370 fizzled)

大量的冒泡火花表示火花的粒度太小,因此接下来我尝试使用策略parListChunk,该策略将列表分成多个块并为每个块创建一个火花。块大小为时,我得到了最好的结果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多核支持的文章,它谈到每个内核都有一个HEC(Haskell执行上下文)。每个HEC都包含一个分配区域(这是单个共享堆的一部分)。每当任何HEC的分配区域都用尽时,必须执行垃圾收集。似乎是一个用于控制它的RTS选项 -A。我试过-A32M,但没什么区别。

EDIT2: 这是专用于此问题的github存储库的链接。我已将分析结果包含在分析文件夹中。

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.The类型的使用随机彩车colorPixel为:

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

2
您能在尝试的地方提供确切的提交concat $ withStrategy …吗?我无法在 6008010,这是与您的修改最接近的提交。
Zeta 2015年

3
我做了一个专门的仓库,所以我不会在里面不小心乱七八糟。我还包括了所有性能分析信息。
贾斯汀·雷蒙德 Justin Raymond)2015年

@dfeuer当我说定义自己的策略时,我不是故意的 Strategy。应该选一个更好的词。此外,堆溢出问题与发生parListChunkparBuffer太。
贾斯汀·雷蒙德

Answers:


2

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

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

新数据以512kb的“托儿所”分配。一旦用尽,就会出现“次要GC”-它扫描托儿所并释放未使用的值。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.