代码之家  ›  专栏  ›  技术社区  ›  Rafael S. Calsaverini

Haskell-Control.Parallel中的多核编程

  •  10
  • Rafael S. Calsaverini  · 技术社区  · 15 年前

    我正在努力学习如何使用电脑 Control.Parallel

    我正在尝试运行以下代码( 谎言 ).

    import Control.Parallel
    
    fib :: Int -> Int
    fib 0 = 0
    fib 1 = 1
    fib n = p `par` (q `pseq`  (p + q))
        where
          p = fib (n-1)
          q = fib (n-2)
    
    
    main = print $ fib 30
    

    我是用以下方法编写的:

    ghc -O2 --make -threaded fibs.hs
    

    然后我执行这个程序得到以下结果(Python脚本的输出,它运行每个程序100次,并返回执行时间的平均和标准偏差):

    ./fibs +RTS -N1 -> avg= 0.060203 s, deviation = 0.004112 s  
    ./fibs +RTS -N2 -> avg= 0.052335 s, deviation = 0.006713 s  
    ./fibs +RTS -N3 -> avg= 0.052935 s, deviation = 0.006183 s  
    ./fibs +RTS -N4 -> avg= 0.053976 s, deviation = 0.007106 s  
    ./fibs +RTS -N5 -> avg= 0.055227 s, deviation = 0.008598 s  
    ./fibs +RTS -N6 -> avg= 0.055703 s, deviation = 0.006537 s  
    ./fibs +RTS -N7 -> avg= 0.058327 s, deviation = 0.007526 s  
    

    1. 当我评估时,到底发生了什么:

      a `par` (b `pseq` (a + b))   ?
      

      我知道 par pseq

    2. 我在一个英特尔公司运行这个 Core 2 四元机。我希望使用-N5或-N6运行不会对性能产生真正的影响,或者程序实际上会开始执行得非常糟糕。但是为什么我没有看到从-N2到-N3的改进,为什么最初的改进如此之小?

    4 回复  |  直到 14 年前
        1
  •  16
  •   Community CDub    8 年前

    作为堂 explained ,问题是你制造了太多火花。下面是如何重写它以获得良好的加速。

    import Control.Parallel
    
    cutoff :: Int
    cutoff = 20
    
    parFib :: Int -> Int
    parFib n | n < cutoff = fib n
    parFib n = p `par` q `pseq` (p + q)
        where
          p = parFib $ n - 1
          q = parFib $ n - 2
    
    fib :: Int -> Int
    fib 0 = 0
    fib 1 = 1
    fib n = fib (n - 1) + fib (n - 2)
    
    main :: IO ()
    main = print $ parFib 40
    

    演示:

    [computer ~]$ ghc --make -threaded -O2 Main.hs
    [1 of 1] Compiling Main             ( Main.hs, Main.o )
    Linking Main ...
    [computer ~]$ time ./Main +RTS -N1
    102334155
    
    real    0m1.509s
    user    0m1.450s
    sys     0m0.003s
    [computer ~]$ time ./Main +RTS -N2
    102334155
    
    real    0m0.776s
    user    0m1.487s
    sys     0m0.023s
    [computer ~]$ time ./Main +RTS -N3
    102334155
    
    real    0m0.564s
    user    0m1.487s
    sys     0m0.030s
    [computer ~]$ time ./Main +RTS -N4
    102334155
    
    real    0m0.510s
    user    0m1.587s
    sys     0m0.047s
    [computer ~]$ 
    
        2
  •  13
  •   Don Stewart    15 年前

    您创建的火花数量是指数级的(想想您在这里创建了多少递归调用)。要真正获得良好的并行性,您需要创建 较少的 在这种情况下,并行工作,因为您的硬件无法处理那么多线程(因此GHC不会制造线程)。

    解决方案是使用切断策略,如本演讲中所述: http://donsbot.wordpress.com/2009/09/05/defun-2009-multicore-programming-in-haskell-now/

        3
  •  3
  •   LarsH    14 年前

    因为没有人给出一个明确的答案 pseq ,这是 official description :

    语义上与seq相同,但是 有着微妙的操作差异: seq的两个论点都很严格, 重新排列 seq b变成b 序号 A. B这通常没有问题 当使用seq表示严格性时, 但是,当 因为我们需要更多的控制权 评价顺序;我们可能想 在b之前评估a,因为我们知道 b已经被点燃了

    这就是为什么我们有pseq。相反 这限制了 这样做,并确保用户可以 秩序。

        4
  •  1
  •   Conrad Meyer    15 年前

    关于(1): par 允许 a pseq 表现得很像一个女人 seq :它强制首先计算第一个结果(好, 序号 不能保证做到这一点,但在GHC的实践中确实如此)。所以在这种情况下,计算 A. b A. B .

    Re(2):这是一个非常琐碎的计算,被转移到其他线程;cpu自己计算的速度可能也一样快。我打赌线程的开销对这个简单计算的影响几乎和帮助一样大。

    推荐文章