【问题标题】:Profiling multithreading performance in a Haskell program — no speedups using parallel strategies在 Haskell 程序中分析多线程性能——使用并行策略没有加速
【发布时间】:2012-11-13 03:09:51
【问题描述】:

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

绿色表示正在运行,橙色表示垃圾回收。 这里的绿色竖条表示 spark 创建,蓝色条表示并行 GC 请求,浅蓝色条表示线程创建。 标签是: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% 的时间,正如上面所预期的那样。

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

【问题讨论】:

  • 你有代码,或者至少是代码的相关部分吗?
  • 从我拥有的程序中提取相关代码后,剩下的就是我上面概述的描述,所以我发现包含任何实际代码没有帮助。我也看不出我使用 fmap 的函数有什么关系,只要它是纯的(它是)。无论如何,这应该是可并行的,即使可遍历结构的构造可能不是。
  • 创建一个小仓库。可能,您会自己发现问题。如果没有,我们可以查看 repo。
  • 你无法弄清楚的问题是,它们往往出现在你没想到的地方。 :-) 英语是一种用于描述计算机程序的非常模糊的语言,它不是人们可以自己编译和尝试的语言。

标签: multithreading haskell parallel-processing profiling


【解决方案1】:

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

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

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 T S 的 Traversable 实例来执行多线程(使用 Data.Functor.Compose)。

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

现在看起来好多了!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-04-18
    • 1970-01-01
    • 2014-10-27
    • 1970-01-01
    • 1970-01-01
    • 2011-03-17
    • 1970-01-01
    相关资源
    最近更新 更多