此答案也可在http://lpaste.net/143207 以 .lhs 文件的形式获得
基本思想是,只要你有这样的表达式:
map f xs
您可以将其替换为:
parMap strat f xs
以火花的形式产生地图计算,以便它将
在线程运行时并行执行。
strat 的典型选择是 rdeepseq - 有关其他选项,请参阅 Basic Strategies。
问题是每次调用f 时都会产生
火花可能不具有成本效益。实现任何加速
您可能必须组织工作,以便火花负责
用于在列表的一系列元素上调用 f。
让我们这样写isPrime:
-- original version
isPrime0 n = and $ map notfactor [2..limit]
where limit = div n 2
notfactor f = mod n f /= 0
(我故意扩大了因子测试范围,所以我们不
我们的测试必须使用大素数。)
第一个想法是简单地将map 更改为parMap rdeepseq:
-- spawn each call to notfactor as a spark
isPrime1 n = and $ parMap rdeepseq notfactor [2..limit]
where limit = div n 2
notfactor f = mod n f /= 0
但是,如果您对此进行基准测试,您会发现
这个运行速度比顺序版本慢很多。
下一个想法是将[2..limit]范围分解为
像这样的少量块:
-- evaluate the notfactor calls in ranges -- not parallized
isPrime2 n = and $ map (\r -> all notfactor r) ranges
where limit = div n 2 -- ceiling $ sqrt $ fromIntegral n
notfactor f = mod n f /= 0
ranges = chunks 3 limit 10
这里的chunks a b k 是一个将
将[a..b] 列为k 等大小的范围。
为了获得并行版本,我们更改了 map 调用
进入parMap rdeepseq:
-- evaluate the notfacto calls in ranges - parallelized
isPrime3 n = and $ parMap rdeepseq (\r -> all notfactor r) ranges
where limit = div n 2
notfactor f = mod n f /= 0
ranges = chunks 3 limit 10
素数 15485863 和
RTS 选项-N1 与-N2:
-N1 -N2
isPrime0 0.624 0.673
isPrime1 12.--- 12.---
isPrime2 0.573 0.603
isPrime3 0.563 0.365
如您所见,isPrime3 确实表现出一些加速。 isPrime1 的时机是因为它产生了数百万个火花,而 isPrime3 仅产生 10 个火花。
为了完整起见,这里是chunks 的代码和
程序驱动程序。
-- divide a range into equal size chunks
chunks :: Integer -> Integer -> Integer -> [[Integer]]
chunks a b k =
let (q,r) = divMod (b - a) k
sizes = replicate (fromIntegral r) (q+1) ++ replicate (fromIntegral (k-r)) q
go x [] = []
go x (y:ys) = [x..x+y-1] : go (x+y) ys
in go a sizes
main :: IO ()
main = do
( which : ps : _ ) <- getArgs
let p = read ps
case which of
"0" -> print $ isPrime0 p
"1" -> print $ isPrime1 p
"2" -> print $ isPrime2 p
"3" -> print $ isPrime3 p