两个素数滤波器实现的性能比较

时间:2011-05-22 02:52:35

标签: performance list haskell

我有两个程序来查找素数(只是一个练习,我正在学习Haskell)。一旦使用ghc(带有标志-O)编译,“primes”比“primes2”快约10倍。然而,在“primes2”中,我认为它只考虑除数测试的素数,这应该比考虑“isPrime”中的奇数更快,对吧?我错过了什么?

isqrt :: Integral a => a -> a  
isqrt = floor . sqrt . fromIntegral

isPrime :: Integral a => a -> Bool  
isPrime n = length [i | i <- [1,3..(isqrt n)], mod n i == 0] == 1

primes :: Integral a => a -> [a]  
primes n = [2,3,5,7,11,13] ++ (filter (isPrime) [15,17..n])

primes2 :: Integral a => a -> [a]  
primes2 n = 2 : [i | i <- [3,5..n], all ((/= 0) . mod i) (primes2 (isqrt i))]

2 个答案:

答案 0 :(得分:2)

我认为这里发生的事情是isPrime是一个简单的循环,而primes2是递归调用自身的 - 它的递归模式看起来像指数一样。

通过我的旧源代码搜索,我找到了这段代码:

primes :: [Integer]
primes = 2 : filter isPrime [3,5..]

isPrime :: Integer -> Bool
isPrime x = all (\n -> x `mod` n /= 0) $
            takeWhile (\n -> n * n <= x) primes

使用已生成的素数列表,仅针对x下的素数测试每个可能的素数sqrt(x)。所以它可能不会多次测试任何给定的素数。

Haskell中的记事:

Haskell中的记忆通常是显式,而不是隐式。编译器不会“做正确的事情”,但它只会做你告诉它的事情。致电primes2时,

*Main> primes2 5
[2,3,5]
*Main> primes2 10
[2,3,5,7]

每次调用该函数时,它都会重新计算所有结果。它必须。为什么?因为1)你没有保存结果,2)每次调用时答案都不同。

在我上面给出的示例代码中,primes是一个常量(即它具有arity为零),因此在内存中只有一个副本,并且它的部分只被评估一次。

如果要进行memoization,则需要在代码中的某个位置使用arity为零的值。

答案 1 :(得分:0)

我喜欢Dietrich在memoization中所做的事情,但我认为这里也存在数据结构问题。列表不是理想的数据结构。必要时,它们是没有随机访问的lisp样式缺陷单元。套装似乎更适合我。

import qualified Data.Set as S

sieve :: (Integral a) => a -> S.Set a
sieve top = let l = S.fromList (2:3:([5,11..top]++[7,13..top]))
                 iter s c
                    | cur > (div (S.findMax s) 2) = s
                    | otherwise = iter (s S.\\ (S.fromList [2*cur,3*cur..top])) (S.deleteMin c)
                    where cur = S.findMin c
             in iter l (l S.\\ (S.fromList [2,3]))

我知道它有点丑陋,而不是过于陈述,但它运行得相当快。我正在寻找一种方法,使用Set.foldSet.union来复合这些更好的效果。任何其他想要加以解决的想法都将受到赞赏。

PS - 了解(2:3:([5,11..top]++[7,13..top]))如何避免不必要的3倍,例如primes中的15。不幸的是,如果您使用列表并注册排序,这会破坏您的排序,但对于不是问题的集合,这会破坏您的排序。