【问题标题】:Parallelism in simple prime search function简单素数搜索函数中的并行性
【发布时间】:2015-10-17 09:38:25
【问题描述】:

我正在尝试学习 Haskell 中的并发性和并行性。 我刚开始写西蒙·马洛的书。不过,并非一切都清楚。 并且互联网上没有很多(如果有的话)简单的例子。

这只是一个用于学习目的的简单素数搜索功能。 这可以并行化吗?如果是的话,有人可以举一个这个函数的例子吗?

main = mapM_ print [x | x <- [2..], isPrime x]

isPrime :: Integer -> Bool
isPrime n = odd n && all (\ f -> n `mod` f /= 0) [3..(ceiling $ sqrt $ fromIntegral n)]

我知道我可以使用 Strategies 来并行映射列表。 例如,我如何分批测试列表中的因素 8 个,并行进行 8 个测试? 请举个例子。

谢谢。

【问题讨论】:

    标签: haskell


    【解决方案1】:

    此答案也可在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
    

    【讨论】:

      猜你喜欢
      • 2017-06-26
      • 1970-01-01
      • 2016-04-22
      • 1970-01-01
      • 1970-01-01
      • 2016-12-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多