尽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开始运行。