Haskell并行策略疑问:fun1与fun2为何串行执行?
问题核心:主线程抢先求值导致spark被丢弃
你遇到的问题本质是**rparWith rdeepseq会让主线程立即把目标表达式求值到完全范式,刚生成的spark还没机会被其他线程调度就直接作废了**。咱们一步步拆解来看:
为什么fun1和fun2没有spark?
先看rparWith的实际行为:它的定义等价于do { rpar x; s x }——也就是先给x生成一个spark,然后立刻对x应用策略s。
当s是rdeepseq时:
rdeepseq x会立即在主线程把x求值到完全范式(对于Just (sum [1..x])来说,就是计算完sum的结果)。- 这时候刚生成的spark(原本负责求值
x)就完全没用了——主线程已经把活干完了,spark会被直接丢弃,所以你看不到任何spark统计。
你的fun1用了parList rdeepseq,而parList本身就是evalList (rparWith s),所以和fun2的evalList (rparWith rdeepseq)是完全一样的逻辑,自然都不会生成有效spark。
为什么fun3能正常生成spark?
fun3用的是evalList (rpar . force),这个组合的行为和rparWith rdeepseq完全不同:
(rpar . force) x等价于rpar (force x)——这里是把force x作为spark的任务,而不是先生成spark再让主线程求值。force x是一个未求值的thunk(任务是把x求值到范式),rpar会为这个thunk生成spark,主线程不会立即执行它,所以spark可以被其他核心的线程调度执行,最终你能看到正常的spark统计。
书中parPair例子的问题
书中的parPair rdeepseq rdeepseq本质是evalPair (rparWith rdeepseq) (rparWith rdeepseq),和fun1/fun2犯了同样的错误:
- 对元组的每个元素,都是先生成spark,然后主线程立刻用
rdeepseq把元素求值到范式。 - 主线程抢先完成了所有计算,spark根本没机会被调度,所以Threadscope只能看到单核心运行,也没有spark生成。
怎么修改才能让例子并行执行?
如果想让这些例子真正生成spark并行执行,需要让主线程不要抢先求值,把求值任务留给spark:
- 把
rparWith rdeepseq换成rpar . force,比如:fun1Fixed :: [Maybe Integer] fun1Fixed = map f l `using` parList (rpar . force) - 对于书中的parPair例子,修改策略为:
然后用这个策略代替parPairForce :: (NFData a, NFData b) => Strategy (a,b) parPairForce = evalPair (rpar . force) (rpar . force)parPair rdeepseq rdeepseq,就能看到spark生成和多核心运行了。
另外要记得运行程序时加上+RTS -N参数来启用并行(比如./your-program +RTS -N4用4个核心),不过你能看到fun3的spark,说明已经加过了。
内容的提问来源于stack exchange,提问作者Mikekekeke
相关产品推荐
相关产品推荐

