Haskell:为什么以下内容按顺序运行?

3

你好。给出的代码:

import           Control.DeepSeq
import           Control.Exception
import           Control.Parallel
import           Control.Parallel.Strategies
import           System.Environment
import           Text.Printf

l = [34,56,43,1234,456,765,345,4574,58,878,978,456,34,234,1234123,1234,12341234]
f x = Just (sum [1..x])

fun1 :: [Maybe Integer]
fun1 = map f l `using` parList rdeepseq
fun2 :: [Maybe Integer]
fun2 = map f l `using` evalList (rparWith rdeepseq)
fun3 :: [Maybe Integer]
fun3 = map f l `using` evalList (rpar . force)

main :: IO ()
main = print fun1

为什么fun1和fun2会按顺序运行? 根据我理解,rparWith应该会触发其参数的Spark。这里here的答案也是这样说明的。但是对于fun1和fun2,我得到的输出是“SPARKS: 0(0转换,0溢出,0无用,0 GC'd,0失败)”。因此,甚至没有创建Sparks。 fun3按预期工作,并且创建了Sparks。 感谢你的帮助。
更新:我发现rdeepseq使书中的示例(Haskell并行与并发编程)按顺序工作。书上说:
我们可以使用parPair编写一个完全并行评估一对组件的策略: parPair rdeepseq rdeepseq :: (NFData a, NFData b) => Strategy (a,b)
当将此策略应用于一对组件时,parPair调用evalPair,在每个组件上调用rparWith rdeepseq。因此,效果是每个组件都将在并行中完全评估为正常形式。
但是如果我运行
(Just (fib 35), Just (fib 36)) `using` parPair rdeepseq rdeepseq

甚至更多。
(fib 35, fib 36) `using` parPair rdeepseq rdeepseq

Threadscope显示只有一个核心正在运行,且未创建任何火花。

类似于此实现的fibonacci(也来自该书)

fib :: Integer -> Integer
fib 0 = 1
fib 1 = 1
fib n = fib (n-1) + fib (n-2)

你是如何编译和运行这个程序的? - Carl
你需要使用 GHC 选项 -threaded 并使用 +RTS -Nx 运行可执行文件,其中 x 是要使用的核心数。你在测试中这样做了吗? - AJF
是的,使用“ghc -O2 [filename.hs] -threaded -rtsopts -eventlog”进行编译。然后使用“[filename] +RTS -N2 -l”运行。 - Mikekekeke
同时,(Just (fib 35), Just (fib 36)) \using` evalPair (rparWith (rseq . force)) (rparWith (rseq . force))显示了 2 个核心繁忙,但是(Just (fib 35), Just (fib 36)) `using` parPair rdeepseq rdeepseq` 只有一个。据我所知,它们应该是等价的。 - Mikekekeke
2个回答

0

原始论文描述了rdeepseq的作用。

rdeepseq :: NFData a => Strategy a
rdeepseq x = rnf x ‘pseq‘ return x

实际上,如果您使用此定义,则会产生火花,就像您所期望的那样。看起来 rdeepseq 语义已更改(可能是 这里),无论是有意还是无意。我在文档中和更改日志中都没有看到任何说明,因此很可能是个错误。请在其错误跟踪器上创建问题,并要求维护者澄清。


2
看起来这是一个已知的问题,所以我在这里发表了评论 https://github.com/haskell/parallel/issues/17 - Mikekekeke
1
实际问题与rdeepseq毫无关系,而与rparWith有关。请参见最终的票证解决方案。 - dfeuer

0

rparWith 是使用 realWorld# 定义的,这是 GHC 内部深度神奇的值。它的使用方式基本上与应用“函数”(有时称为 accursedUnutterablePerformIO,更正式地称为 unsafeInlinePerformIO)相同。只有在涉及的 IO 实际上非常纯净时,才可以合法地使用它。我们认为,由于 Eval 只用于计算,所以这应该没问题。但实际上,激发线程是一种 IO 效果,而且我们很关心!优化器正在以不幸的方式重新排列这些效果,导致它们最终被删除。修复方法是改用 unsafeDupablePerformIO。那是一个表现更好的“函数”,似乎能解决问题。有关详细信息,请参见 票证

注意:我的初始修复方法有点错误;现在已经再次修改。


猜测,这应该是答案。 - Mikekekeke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接