6

在尝试在 Haskell 程序中添加多线程功能后,我注意到性能根本没有提高。追着它,我从threadscope得到了以下数据:

图 1 绿色表示正在运行,橙色表示垃圾回收。 图 2 图 3 这里的绿色竖条表示 spark 创建,蓝色条表示并行 GC 请求,浅蓝色条表示线程创建。 图 4 标签是:spark created、requesting parallel GC、create thread n、steak from cap 2。

平均而言,我在 4 个内核上只获得了大约 25% 的活动,这与单线程程序相比根本没有任何改进。

当然,如果没有对实际程序的描述,这个问题将是无效的。本质上,我创建了一个可遍历的数据结构(例如树),然后在其上 fmap 一个函数,然后将其输入图像写入例程(解释在程序运行结束时明确的单线程段,过去 15 秒) . 函数的构造和 fmapping 都需要大量的时间来运行,尽管第二个时间稍长一些。

上面的图表是通过在图像写入消耗该数据结构之前为该数据结构添加一个 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

我不确定我可以提供哪些其他有用的信息来帮助回答。分析并没有发现任何有趣的东西:它与单核统计数据相同,只是添加的 IDLE 占用了 75% 的时间,正如上面所预期的那样。

发生了什么阻碍了有用的并行化?

4

1 回答 1

4

很抱歉我无法及时提供代码来帮助受访者。我花了一段时间才弄清楚问题的确切位置。

问题如下:我正在映射一个函数

f :: a -> S b

在可遍历的数据结构上

structure :: T a

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

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

Compose (fmap f structure) `using` parTraversable rdeepseq

代替

Compose $ fmap f structure `using` parTraversable rdeepseq

所以我错误地使用 Compose TS 的 Traversable 实例来执行多线程(使用 Data.Functor.Compose)。

(这看起来应该很容易发现,但我花了一段时间才从代码中提取出上述错误!)

现在看起来好多了!

图 1

图 2

于 2012-11-26T07:05:31.457 回答