我一直在C ++和Haskell中实现Sieve of Eratosthenes。目前为了计算不到10亿的素数,C ++版本在12秒内运行,Haskell版本在24秒内运行。
我对Haskell在C ++方面的表现非常满意,但我想知道是否可以从中获得更多性能。
我使用的算法相当简单,布尔表示每个奇数数字为3或更大。我跳过了。因此翻译索引有一些奇特的数学。我知道尽管如此,它的缓存性能仍然很糟糕,但它只是一个练习而不是性能。无论如何code如下:
sieve :: (Ix a, Integral a) => a -> UArray a Bool
sieve n = runSTUArray $ do
let aLen = n `div` 2 - 1
let last_test_i = (integralSqrt (aLen*2+1) - 1) `div` 2
a <- newArray (0, aLen - 1) False
forM_ [0..(last_test_i)-1] $ \i -> do
isComp <- readArray a i
when (not isComp) $ do
let first = 2*i*(3+i) + 3
let step = i*2 + 3
forM_ [first, first + step .. (aLen-1)] $ \j ->
writeArray a j True
return a
然后我认为forM_
中的列表生成可能会减慢速度。所以我写了以下函数:
myForM_ :: (Monad m) => a -> (a -> Bool) -> (a -> a) -> (a -> m b) -> m ()
myForM_ initV fCont fNext f = myForM_W initV
where myForM_W x = if fCont x then f x >> myForM_W (fNext x) else return ()
这几乎是旧式C for循环的副本。第一个参数是初始值,第二个条件,第三个是决定下一个值的东西,最后一个是动作,或者是C函数的主体。
然后我将forM_
替换为myForM_
,如下所示:
sieve :: (Ix a, Integral a) => a -> UArray a Bool
sieve n = runSTUArray $ do
let aLen = n `div` 2 - 1
let last_test_i = (integralSqrt (aLen*2+1) - 1) `div` 2
a <- newArray (0, aLen - 1) False
--forM_ [0..(last_test_i)-1] $ \i -> do
myForM_ 0 (/= last_test_i) (+1) $ \i -> do
isComp <- readArray a i
when (not isComp) $ do
let first = 2*i*(3+i) + 3
let step = i*2 + 3
--forM_ [first, first + step .. (aLen-1)] $ \j ->
myForM_ first (< aLen) (+step) $ \j ->
writeArray a j True
return a
这导致执行时间从24秒增加到17秒,占Haskell版本和C ++版本差异的一半以上。
我查找了myForM_
的预建版本,但我无法找到。{}我是重新发明轮子还是非标准方法?