尽pipe只有22Mb的内存使用量,Haskell线程堆溢出了吗?

我正在尝试并行化射线追踪器。 这意味着我有一个很长的小计算列表。 香草程序运行在67.98秒的特定场景和13 MB的总内存使用率和99.2%的生产率。

在我的第一次尝试中,我使用了缓冲区大小为50的并行策略parBuffer 。我select了parBuffer因为它只是像消耗火花一样parList列表,并且不会像parList那样parList列表的脊柱,由于名单很长,所以很多记忆。 使用-N2 ,运行时间为100.46秒,总内存使用量为14 MB,生产力为97.8%。 火花信息是: SPARKS: 480000 (476469 converted, 0 overflowed, 0 dud, 161 GC'd, 3370 fizzled)

大部分失败的火花表明火花粒度太小,所以接下来我尝试使用策略parListChunk ,它将列表拆分成块,并为每个块创build一个火花。 我用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%的生产力。 这是成功的,所有的火花正在转换,内存使用率低得多,但速度没有改善。 这里是事件日志configuration文件的一部分的图像。 事件日志配置文件

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

还有什么我可以尝试? 如果需要的话,我可以发布更详细的内存使用情况或事件日志分析信息,但是我没有包括这一切,因为它有很多信息,我不认为所有这些信息都必须包含在内。

编辑:我正在阅读有关Haskell RTS多核支持 ,它谈到有一个HEC(Haskell执行上下文)为每个核心。 除此之外,每个HEC都包含一个分配区(它是单个共享堆的一部分)。 每当任何港灯分配区用完时,必须进行垃圾回收。 似乎是一个RTS选项来控制它,-A。 我试过-A32M,但没有看到任何区别。

编辑2: 这是一个链接到一个github回购专用于这个问题 。 我已将分析结果包含在分析文件夹中。

编辑3:这是相关的代码位:

 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的types是:

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

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

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

新的数据分配在512kb“托儿所”。 一旦耗尽,就会发生“次要GC” – 它扫描苗圃并释放未使用的值。

所以如果你把数据分割成更小的块,你可以使引擎更早地进行清理 – GC开始运行。