【问题标题】:Performance comparison of two implementations of a primes filter素数滤波器的两种实现的性能比较
【发布时间】:2011-05-22 02:52:34
【问题描述】:

我有两个程序来查找素数(只是一个练习,我正在学习 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))]

【问题讨论】:

  • 我不是 100% 确定,但我认为问题在于 primes2 中没有共享;每次您拨打primes2 (isqrt i) 时,您都会重新生成整个列表。
  • 有趣。没有素数2的记忆? (PS:我不确定我是否理解 memoization,请耐心等待)

标签: performance list haskell


【解决方案1】:

我认为这里发生的情况是 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

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

Haskell 中的记忆:

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

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

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

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

如果你想要记忆,你需要在你的代码中的某处有一个零元的值。

【讨论】:

  • 但是...我希望能够记住集合 primes2 的连续值。此外,primes2(在我有限的理解中)比 isPrime 循环 less,因为直到 n 的素数比奇数少。
  • @hammar:这里吹毛求疵,但primes2 是一个值,所有函数也是如此。重要的区别是primes2 具有非零参数,这意味着在实践中,每个应用程序都会产生不同的 thunk。 (咦,好像hammar删了评论……)
  • 该解决方案在编译后执行得非常好。太糟糕了,它不适合单行:-)
  • 没问题!怎么样:primes2 :: [整数] primes2 = 2 : [i | i
  • 几乎总是在性能和​​可读性之间进行权衡。在 Haskell 中,最快的代码通常很聪明,而最聪明的代码通常很慢。
【解决方案2】:

我喜欢 Dietrich 对 memoization 所做的工作,但我认为这里也存在数据结构问题。列表并不是理想的数据结构。它们必然是没有随机访问的 lisp 风格的 cons 单元。套装似乎更适合我。

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。不幸的是,如果您使用列表并注册排序,这会破坏您的排序,但对于集合这不是问题。

【讨论】:

    猜你喜欢
    • 2011-08-12
    • 2012-06-07
    • 1970-01-01
    • 1970-01-01
    • 2014-02-26
    • 2016-05-30
    • 1970-01-01
    • 1970-01-01
    • 2017-06-20
    相关资源
    最近更新 更多