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

标签 haskell parallel-processing raytracing

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

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

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

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

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

运行时间为 95.99 秒,总内存使用量为 22MB,生产率为 98.8%。这是成功的,因为所有的 Spark 都被转换并且内存使用量低得多,但速度没有提高。这是事件日志配置文件的一部分的图像。 Event log profile

正如您所看到的,线程由于堆溢出而被停止。我尝试添加 +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 预先计算和使用的随机 float 。 colorPixel 的类型是:

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

最佳答案

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

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

New data are allocated in 512kb "nursery". Once it's exhausted, "minor GC" occurs - it scans the nursery and frees unused values.

因此,如果您将数据切成更小的 block ,则可以使引擎更早地进行清理 - GC 就会启动。

关于尽管总内存使用量只有 22Mb,但 Haskell 线程堆溢出?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31641464/

相关文章:

python - 消息传递分布式算法框架

list - 列表推导式是 Haskell 的主要部分吗?

java - 从单个图像流有效生成多个缩略图

java - 一个函数如何变成原子的?

python - 需要有关光线追踪算法的建议

haskell - 加速 runhaskell

python - 满足条件时终止所有进程

game-physics - 这个平面射线相交代码正确吗?

c - 我的光线追踪器中的颗粒状球体

windows - Windows 上真正可移植的 Haskell 安装