12

Haskell 提供了一个par组合器,它将一个“spark”排队,以便与当前线程并行进行可能的评估。它还提供了一个pseq组合器,它强制对纯代码的评估以特定的顺序发生。

Haskell 似乎没有提供一种产生多个火花然后等待它们全部完成的方法。使用显式并发实现这一点非常简单,但使用纯火花似乎是不可能的。

在某种程度上,这可能是因为火花的预期用例。它们似乎是为推测性评估而设计的。也就是说,做可能需要但可能不需要的工作。因此,火花仅在空闲的核心上运行。

但是,这不是我的用例。我有一堆我知道的结果,事实上,很快就会需要这些结果。如果我在火花点燃之前开始尝试处理结果,我将再次以一堆失败的火花结束单线程。

当然,par 等待火花完成,它不会实现任何并行性!但是,如果有某种方法可以产生多个火花然后等待它们全部完成,那将非常有用。我找不到任何方法来做到这一点。

有人有什么有用的建议吗?(显然,除了“使用显式并发”。)

4

2 回答 2

6

您可以尝试将引发计算的结果放入严格的数据结构中

{-# LANGUAGE BangPatterns #-}
module Main where

import Control.Parallel

fib :: Int -> Int
fib n
    | n < 1     = 0
    | n == 1    = 1
    | otherwise = fib (n-1) + fib (n-2)

trib :: Int -> Int
trib n
    | n < 1     = 0
    | n < 3     = 1
    | otherwise = trib (n-1) + trib (n-2) + trib (n-3)

data R = R { res1, res2 :: !Int }

main :: IO ()
main = do
    let !r = let a = fib 38
                 b = trib 31
             in a `par` b `pseq` (R a b)
    print $ res1 r
    print $ fib 28
    print $ res2 r

这在这里工作:

$ ./spark +RTS -N2 -s
39088169
317811
53798080
          65,328 bytes allocated in the heap
           9,688 bytes copied during GC
           5,488 bytes maximum residency (1 sample(s))
          30,680 bytes maximum slop
               2 MB total memory in use (0 MB lost due to fragmentation)

                                    Tot time (elapsed)  Avg pause  Max pause
  Gen  0         1 colls,     0 par    0.00s    0.00s     0.0001s    0.0001s
  Gen  1         1 colls,     1 par    0.00s    0.00s     0.0001s    0.0001s

  Parallel GC work balance: 1.33 (686 / 515, ideal 2)

                        MUT time (elapsed)       GC time  (elapsed)
  Task  0 (worker) :    0.59s    (  0.59s)       0.00s    (  0.00s)
  Task  1 (worker) :    0.00s    (  0.59s)       0.00s    (  0.00s)
  Task  2 (bound)  :    0.59s    (  0.59s)       0.00s    (  0.00s)
  Task  3 (worker) :    0.00s    (  0.59s)       0.00s    (  0.00s)

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

  INIT    time    0.00s  (  0.00s elapsed)
  MUT     time    1.17s  (  0.59s elapsed)
  GC      time    0.00s  (  0.00s elapsed)
  EXIT    time    0.00s  (  0.00s elapsed)
  Total   time    1.18s  (  0.59s elapsed)

  Alloc rate    55,464 bytes per MUT second

  Productivity  99.9% of total user, 199.1% of total elapsed

gc_alloc_block_sync: 0
whitehole_spin: 0
gen[0].sync: 0
gen[1].sync: 0
于 2012-08-06T13:04:04.683 回答
6

真正简短的答案

你不能。

简短的回答

要等待火花完成,请尝试评估火花正在评估的内容。例如,如果你有表达式ab, 来计算a + b,你可以做

a `par` b `par` (a + b)

或者

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

长答案

当您使用 来创建火花时par,您是在告诉运行时系统“我稍后将需要此值,因此您应该并行评估它。” 当您稍后需要该值时,火花已经评估了表达式,或者没有。如果有,thunk 将被一个值替换,因此重新评估没有成本 - 它只是获取值。如果它没有被评估为火花,那么等待火花是没有用的——它可能需要一段时间才能被安排,而线程等待是在浪费时间。您应该自己评估表达式,而不是等待。本质上,无需等待火花。您只需尝试评估原始表达式并获得性能优势。

另外,关于投机的说明 - 虽然火花可以并且经常用于投机,但这并不完全是它们的设计目的。我看到par它被用于简单的并行化,pfib如下所示,比我看到它用于推测的次数要多得多。

例子

一个标准的例子是并行化斐波那契数,从串行

fib 0 = 0
fib 1 = 1
fib n = fib (n - 1) + fib (n - 2)

到平行

pfib 0 = 0
pfib 1 = 1
pfib n = l `par` r `pseq` (l + r) where
    l = pfib (n - 1)
    r = pfib (n - 2)

.

现在举一个使用推测的例子:

spec :: a -- a guess to the input value
    -> (a -> b) -- a function to tranform the input value
    -> a -- the actual input value - this will require computation
    -> b -- the function applied to the input value
spec guess f input = let speculation = f guess in speculation `par`
    if guess == input
        then speculation
        else f input

我从中得到的 hackage 包,推测,实际上有一些优化,比如不在单个核心上执行此操作并检查输入是否已经被评估,但这与函数的工作无关。

其他使事情更明确的解决方案

  • monad-par
  • 策略,即利用par
  • 与 IO 混为一谈。这里有很多东西。
于 2012-08-07T04:19:52.720 回答