使用替代'forM_`来优化Eratosthenes的筛选

时间:2014-10-26 18:05:20

标签: haskell sieve-of-eratosthenes

我一直在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_的预建版本,但我无法找到。{}我是重新发明轮子还是非标准方法?

0 个答案:

没有答案