我正在试图找出Haskell的Control.Parallel模块,我编写了以下内容(tMap是时间比较的基础案例):
import Control.Parallel
paraMap, tMap :: (a -> b) -> [a] -> [b]
paraMap _ [] = []
paraMap f [x] = [f x]
paraMap f (x : xs@(y : ys)) = (f y `par` f x) : paraMap f xs
tMap _ [] = []
tMap f (x : xs) = f x : tMap f xs
想法是paraMap将在完成当前元素之前开始计算下一个元素。我的测试显示它的性能比tMap差。我猜这是因为par引入了更多的开销,并且因为我没有正确使用它,所以它没有产生足够的收益来克服成本。
我上面的例子出了什么问题?据我了解,x 'par' y
基本上意味着“我以后需要x
,所以要并行计算,但现在返回y
”(类似于seq如何工作,但体现在火花中,它允许在单独的线程上计算它。)
我唯一能想到的是它被混淆了,因为我需要在函数的单独实例中使用下一个元素(recurse)。我想我可以使paraMap
成为实际递归的包装器,并明确地将下一个par
'd元素传递给下一个递归器;但这看起来很笨拙。
我尝试使用:! ghc -o -threaded <fileName> "<fileName><fileExt>" +RTS -s
(格式化为GHCi中的工具)编译它,并将以下内容作为主要内容进行调试:
main = print $ paraMap (\x -> foldl (+) 0 [1..x * 100]) [1..500]
并收到以下反馈:
75,657,304 bytes allocated in the heap
32,738,472 bytes copied during GC
8,926,880 bytes maximum residency (7 sample(s))
139,208 bytes maximum slop
19 MB total memory in use (0 MB lost due to fragmentation)
Tot time (elapsed) Avg pause Max pause
Gen 0 82 colls, 0 par 0.02s 0.04s 0.0005s 0.0199s
Gen 1 7 colls, 0 par 0.08s 0.12s 0.0168s 0.0587s
TASKS: 5 (1 bound, 4 peak workers (4 total), using -N1)
SPARKS: 0 (0 converted, 0 overflowed, 0 dud, 0 GC'd, 0 fizzled)
INIT time 0.00s ( 0.15s elapsed)
MUT time 0.05s ( 0.30s elapsed)
GC time 0.09s ( 0.16s elapsed)
EXIT time 0.02s ( 0.01s elapsed)
Total time 0.16s ( 0.62s elapsed)
Alloc rate 1,614,022,485 bytes per MUT second
Productivity 40.0% of total user, 10.1% of total elapsed
gc_alloc_block_sync: 0
whitehole_spin: 0
gen[0].sync: 0
gen[1].sync: 0
除非我读错了,否则没有产生火花,这绝对是一个问题。
我是否认为这完全错了?任何见解都将不胜感激。
答案 0 :(得分:5)
在这里,par
导致f y
被引发,但其结果从未使用过(它没有绑定到任何东西,所以你无法访问它):
paraMap f (x : xs@(y : ys)) = (f y `par` f x) : paraMap f xs
f
上的y
的下一个应用(在f x
的递归调用中将发生paraMap
)对此引发的f y
计算一无所知,因为你从未将它传递给它。
一个更简单的例子,遇到同样的问题,就像是
foo x y = bar x `par` bar y `pseq` bar x + bar y
同样,对bar
的第二次调用无法访问已经引发的计算结果。相反,你应该写
foo x y = let x' = bar x
y' = bar y
in x' `par` y' `pseq` x' + y'