Haskell vs C速度:Eratosthenes的筛子

时间:2016-06-17 12:49:50

标签: c performance haskell math implementation

Haskell one使用优化的Data.IntSet实现,复杂度为O(lg n)。但是,尽管Haskell代码已针对偶数情况进行了优化,但n = 2000000的速度差异为15x(之前为30x)。我想知道/为什么我在Haskell中的实现是不完美的。

原始Haskell

primesUpTo :: Int -> [Int]
primesUpTo n = 2 : put S.empty [3,5..n]
    where put :: S.IntSet -> [Int] -> [Int]
          put _ [] = []
          put comps (x:xs) =
            if S.member x comps
            then put comps xs
            else x : put (S.union comps multiples) xs
                where multiples = S.fromList [x*2, x*3 .. n]

更新

fromDistinctAscList提高了4倍的速度。 2-3-5-7-车轮再加速50%。

primesUpTo :: Int -> [Int]
primesUpTo n = 2 : 3 : 5 : 7 : put S.empty (takeWhile (<=n) (spin wheel 11))
    where put :: S.IntSet -> [Int] -> [Int]
          put _ [] = []
          put comps (x:xs) =
            if S.member x comps
            then put comps xs
            else x : put (S.union comps multiples) xs
                where multiples = S.fromDistinctAscList [x*x, x*(x+2) .. n]
          spin (x:xs) n = n : spin xs (n + x)
          wheel = 2:4:2:4:6:2:6:4:2:4:6:6:2:6:4:2:6:4:6:8:4:2:4:2:4:8:6:4:6:2:4:6:2:6:6:4:2:4:6:2:6:4:2:4:2:10:2:10:wheel

基准

所有时间都是通过* nix time命令,真实空间

来衡量的
Haskell original : 2e6: N/A;    2e7: >30s
Haskell optimized: 2e6: 0.396s; 2e7: 6.273s
C++ Set (ordered): 2e6: 4.694s; 2e7: >30s
C++ Bool Array   : 2e6: 0.039s; 2e7: 0.421s

Haskell优化比C ++ Bool慢10~15x,比C ++设置快10倍。

源代码

C编译器选项:g ++ 5.3.1,g++ -std=c++11 Haskell选项:ghc 7.8.4,ghc

C代码(Bool数组)http://pastebin.com/W0s7cSWi

 prime[0] = prime[1] = false;
 for (int i=2; i<=limit; i++) { //edited
     if (!prime[i]) continue;
     for (int j=2*i; j<=n; j+=i)
        prime[j] = false;
 }

C代码(设置)http://pastebin.com/sNpghrU4

 nonprime.insert(1);
 for (int i=2; i<=limit; i++) { //edited
     if (nonprime.count(i) > 0) continue;
     for (int j=2*i; j<=n; j+=i)
        nonprime.insert(j);
 }

Haskell代码http://pastebin.com/HuMqwvRW 代码如上所述。

1 个答案:

答案 0 :(得分:4)

  

我想知道我在Haskell中的实现是否/为何不完美。

而不是fromList,您最好使用执行线性fromDistinctAscList。您也可以添加仅奇数倍从x * x开始而不是x * 2,因为已经添加了所有较小的奇数倍。风格方面,正确的折叠可能比递归更合适。

这样,我的性能提升超过3倍,n等于2,000,000:

import Data.IntSet (member, union, empty, fromDistinctAscList)

sieve :: Int -> [Int]
sieve n = 2: foldr go (const []) [3,5..n] empty
    where
    go i run obs
        | member i obs = run obs
        | otherwise    = i: run (union obs inc)
        where inc = fromDistinctAscList [i*i, i*(i + 2)..n]

尽管如此,数组同时具有O(1)访问权限和缓存友好内存分配。使用可变数组,我发现Haskell代码的性能提升超过15倍(同样n等于2,000,000):

{-# LANGUAGE FlexibleContexts #-}
import Data.Array.ST (STUArray)
import Control.Monad (forM_, foldM)
import Control.Monad.ST (ST, runST)
import Data.Array.Base (newArray, unsafeWrite, unsafeRead)

sieve :: Int -> [Int]
sieve n = reverse $ runST $ do
    arr <- newArray (0, n) False :: ST s (STUArray s Int Bool)
    foldM (go arr) [2] [3,5..n]
    where
    go arr acc i = do
        b <- unsafeRead arr i
        if b then return acc else do
            forM_ [i*i, i*(i + 2).. n] $ \k -> unsafeWrite arr k True
            return $ i: acc