在Haskell程序中分析多线程性能-使用并行策略无法提升速度

6
尝试在Haskell程序中添加多线程功能后,发现性能并没有改善。通过threadscope获得以下数据,追踪问题:Graph 1绿色表示运行,橙色表示垃圾回收。Graph 2Graph 3这里,竖直的绿色条表示Spark创建,蓝色条表示并行GC请求,浅蓝色条表示线程创建。Graph 4标签为:spark created,requesting parallel GC,creating thread n和stealing spark from cap 2。
平均而言,在4个核心上只有约25%的活动率,与单线程程序相比没有任何改进。当然,如果不描述实际程序,这个问题将是无意义的。基本上,我创建了一个可遍历的数据结构(例如树),然后对其进行函数映射,最后将其馈送到图像写入例程中(解释了程序运行结束后15秒过去的单线程段)。构建和函数映射都需要大量时间来运行,尽管后者稍微需要更多时间。
上述图表是通过在数据结构被图像写入程序消耗之前添加parTraversable策略而制作的。我还尝试过对数据结构使用toList,然后使用各种并行列表策略(parList、parListChunk、parBuffer),但结果每次都相似,而且适用于各种参数(即使使用大块)。我还尝试在函数映射之前完全评估可遍历的数据结构,但仍然出现了完全相同的问题。
这里是一些其他统计信息(针对同一程序的不同运行):
   5,702,829,756 bytes allocated in the heap
     385,998,024 bytes copied during GC
      55,819,120 bytes maximum residency (8 sample(s))
       1,392,044 bytes maximum slop
             133 MB total memory in use (0 MB lost due to fragmentation)

                                    Tot time (elapsed)  Avg pause  Max pause 
  Gen  0     10379 colls, 10378 par    5.20s    1.40s     0.0001s    0.0327s
  Gen  1         8 colls,     8 par    1.01s    0.25s     0.0319s    0.0509s

  Parallel GC work balance: 1.24 (96361163 / 77659897, ideal 4)

                        MUT time (elapsed)       GC time  (elapsed)
  Task  0 (worker) :    0.00s    ( 15.92s)       0.02s    (  0.02s)
  Task  1 (worker) :    0.27s    ( 14.00s)       1.86s    (  1.94s)
  Task  2 (bound)  :   14.24s    ( 14.30s)       1.61s    (  1.64s)
  Task  3 (worker) :    0.00s    ( 15.94s)       0.00s    (  0.00s)
  Task  4 (worker) :    0.25s    ( 14.00s)       1.66s    (  1.93s)
  Task  5 (worker) :    0.27s    ( 14.09s)       1.69s    (  1.84s)

  SPARKS: 595854 (595854 converted, 0 overflowed, 0 dud, 0 GC'd, 0 fizzled)

  INIT    time    0.00s  (  0.00s elapsed)
  MUT     time   15.67s  ( 14.28s elapsed)
  GC      time    6.22s  (  1.66s elapsed)
  EXIT    time    0.00s  (  0.00s elapsed)
  Total   time   21.89s  ( 15.94s elapsed)

  Alloc rate    363,769,460 bytes per MUT second

  Productivity  71.6% of total user, 98.4% of total elapsed

我不确定我能提供什么其他有用的信息来帮助回答。分析显示没有任何有趣的东西:除了一个预期中占据75%时间的IDLE之外,它与单核统计数据相同。
是什么导致了有用并行化无法实现?

你有代码,或者至少是相关部分的代码吗? - shachaf
一旦我从程序中提取出相关的代码,剩下的就是我上面概述的非常精确的描述,所以我认为包含任何实际代码并没有什么帮助。我也不明白我使用fmap函数的功能有什么关系,只要它是纯的(它是),那么无论如何都应该是可并行化的,即使可遍历结构的构建可能不是。 - Will
创建一个小仓库。也许你自己在这个过程中会发现问题。如果没有,我们可以查看这个仓库。 - usr
5
无法解决的问题常常出现在你没有考虑到的地方。英语对于描述计算机程序来说比较模糊,人们不能编译并自己尝试一些东西。 - shachaf
1个回答

4
抱歉我不能及时提供代码来帮助回答者。花费了一段时间才梳理出问题的确切位置。
问题如下:我正在对一个函数进行fmapping。
f :: a -> S b

在可遍历的数据结构上方
structure :: T a

其中S和T是两个可遍历的functor。

然后,在使用parTraversable时,我错误地写成了

Compose (fmap f structure) `using` parTraversable rdeepseq

替代

Compose $ fmap f structure `using` parTraversable rdeepseq

我曾错误地使用Compose T S的可遍历实例来进行多线程操作(使用Data.Functor.Compose)。这看起来似乎很容易发现,但我花了一段时间从代码中找到上述错误!现在看起来好多了!Graph 1Graph 2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接