我有一个程序,我正在尝试并行化(完全粘贴与可运行代码
here).
我已经分析过并发现大部分时间都花在了findNearest上,它基本上是一个大型Data.Map的简单折叠器.
findNearest :: RGB -> M.Map k RGB -> (k,Word32) findNearest rgb m0 = M.foldrWithKey' minDistance (k0,distance rgb r0) m0 where (k0,r0) = M.findMin m0 minDistance k r x@(_,d1) = -- Euclidean distance in RGB-space let d0 = distance rgb r in if d0 < d1 then (k,d0) else x
parFindNearest应该在较大Map的子树上并行执行findNearest.
parFindNearest :: NFData k => RGB -> M.Map k RGB -> (k,Word32) parFindNearest rgb = minimumBy (comparing snd) . parMap rdeepseq (findNearest rgb) . M.splitRoot
不幸的是,GHC GC在转换为有用的并行性之前是我最大的火花.
这是使用ghc -O2 -thaded编译并使用RTS -s -N2运行的结果
839,892,616 bytes allocated in the heap 123,999,464 bytes copied during GC 5,320,184 bytes maximum residency (19 sample(s)) 3,214,200 bytes maximum slop 16 MB total memory in use (0 MB lost due to fragmentation) Tot time (elapsed) Avg pause Max pause Gen 0 1550 colls,1550 par 0.23s 0.11s 0.0001s 0.0004s Gen 1 19 colls,18 par 0.11s 0.06s 0.0030s 0.0052s Parallel GC work balance: 16.48% (serial 0%,perfect 100%) TASKS: 6 (1 bound,5 peak workers (5 total),using -N2) SPARKS: 215623 (1318 converted,0 overflowed,0 dud,198111 GC'd,16194 fizzled) INIT time 0.00s ( 0.00s elapsed) MUT time 3.72s ( 3.66s elapsed) GC time 0.34s ( 0.17s elapsed) EXIT time 0.00s ( 0.00s elapsed) Total time 4.07s ( 3.84s elapsed) Alloc rate 225,726,318 bytes per MUT second Productivity 91.6% of total user,97.1% of total elapsed gc_alloc_block_sync: 9862 whitehole_spin: 0 gen[0].sync: 0 gen[1].sync: 2103
正如您所看到的,大多数火花在转换之前都是GC或fizzle.我已尝试尝试不同的严格性,findNearest返回自定义严格对数据类型而不是元组
,或者使用来自Control.Parallel.Strategies的rdeepseq,但我的火花仍然是GC’d.
我想知道
>为什么在转换之前我的火花是GC?
>如何更改程序以利用并行性?
解决方法
我不是并行策略方面的专家,所以我可能完全错了.但:
如果通过设置足够大的分配区域来禁用GC(例如使用-A20M运行时选项),您将看到大多数火花都是失败的,而不是GC.这意味着它们在相应的火花结束之前通过普通程序流程进行评估.
minimumBy立即强制parMap结果,开始评估它们.与此同时,火花被安排和执行,但为时已晚.当spark完成后,该值已由主线程评估.如果没有-A20M,火花就会被GC进行,因为即使在安排火花之前,也会评估该值并进行GC测量.
这是一个简化的测试用例:
import Control.Parallel.Strategies f :: Integer -> Integer f 0 = 1 f n = n * f (n - 1) main :: IO () main = do let l = [n..n+10] n = 1 res = parMap rdeepseq f l print res
在这种情况下,所有的火花都会失败:
SPARKS: 11 (0 converted,0 GC'd,11 fizzled)
(有时他们是GC’d)
但是如果我在打印结果之前产生主线程,
import Control.Parallel.Strategies import Control.Concurrent f :: Integer -> Integer f 0 = 1 f n = n * f (n - 1) main :: IO () main = do let l = [n..n+10] n = 1 res = parMap rdeepseq f l res `seq` threadDelay 1 print res
然后所有火花都被转换:
SPARKS: 11 (11 converted,0 fizzled)
所以,看起来你没有足够的火花(尝试在我的例子中设置l = [n..n 1000]),并且它们不够重(在我的例子中尝试设置n = 1000).