考虑以下计划。
import Control.Parallel
import Control.Parallel.Strategies
main = do
r <- evaluate (runEval test)
print r
test = do
a <- rpar (fib 36)
b <- rpar (fib 37)
return (a,b)
fib :: Int -> Integer
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)
请注意,我们故意使用Fibonacci生成器的低效实现来进行计算需要一些时间。现在我们用GHC编译这个程序:
$ ghc rpar.hs -threaded
该程序带有+RTS -N1
标志的 3.281s ,带有+RTS -N2
标志的 2.126s 。现在我们将print r
替换为print "hello"
函数中的main
,并以相同的方式编译修改后的程序。新计划的+RTS -N1
0.003s ,+RTS -N2
0.004s 。似乎a
函数中的b
和test
未在新程序中计算。
然后,我们以test
样式修改rpar/rseq
函数:
test = do
a <- rpar (fib 36)
b <- rseq (fib 37)
return (a,b)
我们对此计划进行了相同的实验。 (1)print r
函数main
:该程序为+RTS -N1
和+RTS -N2
采用 3.283s 和 2.138s 标志分别; (2)print "hello"
函数main
:+RTS -N1
和+RTS -N2
的程序需要 1.956s 和 2.025s 分别。显然,在这种情况下,a
中的b
或test
都会被计算出来。
我在这个例子中有两个问题:
(1)何时在程序中实际计算出rpar
和rseq
个表达式?在评估(fib 36)
时,似乎不会立即计算a <- rpar (fib 36)
。
(2)如果一台机器有足够的CPU核心并且我们在运行程序时指定了+RTS -N2
标志,则a
和b
的计算保证同时(或几乎同时)启动?
答案 0 :(得分:3)
更大的问题是:何时在Haskell中计算事物?
由于Haskell是一种懒惰的语言,我们只有在需要这种东西时才会计算出事物。特别是在您的第一个程序中,如果您从不要求r
(evaluate
只会将其计算到(,)
构造函数),则fib
调用永远不会被计算,并行性只是开销。
在你的第二个程序rseq
将要求包含计算的结果,因此无论你是否print
,都会计算它。
使用+RTS -N2
运行程序将使运行时使用2个Haskell执行上下文(HEC)。计算a
和b
将添加到火花池中,并可供HEC计算。接下来会发生什么是GHC Runtime魔术,但如果计算不是太简单,你可以假设每个HEC都需要一个火花并计算它。
有关RTS的更多阅读,请查看此论文集:https://ghc.haskell.org/trac/ghc/wiki/ReadingList#DataParallelHaskellandconcurrency(以及运行时间段),以及可能的Simon Marlow http://chimera.labs.oreilly.com/books/1230000000929