我正在尝试并行化一个光线跟踪器。这意味着我有一个很长的小计算列表。vanilla程序在特定场景下运行,运行时间为67.98秒,总内存使用量为13MB,生产率为99.2%。
在我的第一次尝试中,我使用了缓冲区大小为50的并行策略parBuffer
。我之所以选择parBuffer
,是因为它遍历列表的速度和消耗的火花一样快,并且不会像parList
那样强制使用列表的主干,因为列表非常长,这将占用大量内存。使用-N2
时,它的运行时间为100.46秒,总内存使用量为14MB,生产率为97.8%。spark信息是:SPARKS: 480000 (476469 converted, 0 overflowed, 0 dud, 161 GC'd, 3370 fizzled)
大量冒泡的火花表明火花的粒度太小,所以接下来我尝试使用策略parListChunk
,它将列表拆分成块,并为每个块创建一个火花。我用块大小的0.25 * imageWidth
得到了最好的结果。该程序运行时间为93.43秒,总内存使用量为236MB,生产率为97.3%。spark信息是: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
,它会将默认堆大小一直增加到1 1Gb。结果没有改变。我读到Haskell主线程会在堆栈溢出时使用堆中的内存,所以我也尝试用+RTS -M1G -K1G
增加默认堆栈的大小,但这也没有影响。
还有什么我可以试一试的吗?如果需要,我可以发布关于内存使用或事件日志的更详细的概要信息,我没有全部包括,因为它是大量的信息,我认为没有必要包括所有这些信息。
编辑:我读到了关于Haskell RTS multicore support的文章,它谈到每个核心都有一个HEC (Haskell执行上下文)。每个HEC都包含一个分配区(它是单个共享堆的一部分)。每当任何HEC的分配区域耗尽时,都必须执行垃圾收集。这似乎是一个控制它的RTS option,-A。我尝试过-A32M,但看不出有什么不同。
EDIT2: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.The预先计算和使用的随机浮点数,colorPixel
类型为:
colorPixel :: World -> ((Float,Float),([(Float,Float)],[(Float,Float)])) -> Color
https://stackoverflow.com/questions/31641464
复制相似问题