Mat*_*hid 12 parallel-processing haskell
Haskell提供了一个par组合器,它将一个"火花"排队,以便与当前线程并行进行可能的评估.它还提供了一个pseq组合器,它强制评估纯代码以特定顺序发生.
Haskell似乎没有提供的是产生几个火花的方法,然后等待它们全部完成.使用显式并发实现这一点非常简单,但纯粹的火花似乎是不可能的.
在某种程度上,这可能是因为火花的预期用例.它们似乎是为投机评估而设计的.也就是说,做可能需要但可能不需要的工作.因此,火花仅在核心上运行,否则它们是空闲的.
但是,这不是我的用例.我知道很多结果,事实上很快就会需要.如果我在火花爆发之前开始尝试处理结果,我将再次以一堆失败的火花结束单线程.
当然,par 等待火花完成,它不会达到任何并行性!但如果有某种方法可以产生几个火花然后等待它们全部完成,那将是非常有用的.我找不到任何方法可以做到这一点.
有没有人有任何有用的建议?(显然,除了"使用显式并发"之外).
您可以尝试将引发计算的结果放入严格的数据结构中
{-# 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
Run Code Online (Sandbox Code Playgroud)
这在这里工作:
$ ./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
Run Code Online (Sandbox Code Playgroud)
真的很短的答案
你不能.
简答
等待火花完成,尝试评估火花评估的内容.例如,如果您有表达式,a并且b要计算a + b,您可以这样做
a `par` b `par` (a + b)
Run Code Online (Sandbox Code Playgroud)
要么
a `par` b `pseq` (a + b)
Run Code Online (Sandbox Code Playgroud)
答案很长
当你通过使用创建一个spark时par,你告诉运行时系统"我稍后会需要这个值,所以你应该并行计算它." 当您稍后需要该值时,要么spark已经评估了表达式,要么它没有.如果有,则thunk将被一个值替换,因此重新评估没有成本 - 它只是获取值.如果它没有被评估为火花那么等待火花是没用的 - 可能需要一段时间来安排,而线程等待是浪费时间.而不是等待,你应该自己评估表达式.基本上,没有必要等待火花.您只需尝试评估原始表达式并获得性能优势.
此外,关于猜测的一个注释 - 虽然火花可以并且经常被用于推测,但这并不完全是它们的设计目的.我看到par用于简单的并行化,pfib如下所示,比我用于猜测的频率更高.
例子
一个标准的例子是从序列中并行化Fibonacci数
fib 0 = 0
fib 1 = 1
fib n = fib (n - 1) + fib (n - 2)
Run Code Online (Sandbox Code Playgroud)
并行
pfib 0 = 0
pfib 1 = 1
pfib n = l `par` r `pseq` (l + r) where
l = pfib (n - 1)
r = pfib (n - 2)
Run Code Online (Sandbox Code Playgroud)
.
现在举一个使用推测的例子:
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
Run Code Online (Sandbox Code Playgroud)
我从中得到的hackage包,推测,实际上有一些优化,比如不在单核上执行此操作并检查输入是否已经被评估,但这与函数的工作无关.
使事情更明确的其他解决方案