GHC GC'ing火花

我有一个我试图并行化的程序(在这里完全粘贴可运行代码)。

我异型,发现大部分的时间都花在findNearest这基本上是一个简单的foldr在大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应该在较大的MapfindNearest并行执行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 -threaded +RTS -s -N2编译并使用+RTS -s -N2运行的+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'd或失败。 我试着尝试不同的严格性,让findNearest返回一个自定义的严格配对数据类型而不是一个元组,或者使用Control.Parallel.Strategies rdeepseq,但我的火花仍然是GC'd。

我想知道

  • 为什么我的火花在被转换之前被GC'd?
  • 我如何改变我的程序以利用并行性?

  • 我不是平行策略专家,所以我可能完全错误。 但:

    如果您通过设置足够大的分配区域来禁用GC(例如,使用-A20M运行时选项),您将看到大多数火花熄灭,而不是GC'd。 这意味着它们在相应的火花完成之前通过普通程序流程进行评估。

    minimumBy立即强制parMap结果,开始评估它们。 同时,火花计划和执行,但已为时过晚。 火花完成后,该值已由主线程评估。 如果没有-A20M ,则火花是GC'd,因为在计划火花之前,该值将被评估并GC'd。

    这是一个简化的测试用例:

    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 overflowed, 0 dud, 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 overflowed, 0 dud, 0 GC'd, 0 fizzled)
    

    所以,看起来你没有足够的火花(在我的例子中尝试设置l = [n..n+1000] ),并且它们不够重(在我的例子中尝试设置n = 1000 )。

    链接地址: http://www.djcxy.com/p/78409.html

    上一篇: GHC GC'ing sparks

    下一篇: Bash script doesn't catch SIGINT while in read loop