【发布时间】:2014-01-19 20:02:29
【问题描述】:
我开始尝试了解 haskell 的性能,以及是什么让事情变得快和慢,对此我有点困惑。
我有一个函数的两个实现,它生成一个直到某个值的素数列表。第一个直接来自 Haskell wiki:
primesTo :: (Ord a, Num a, Enum a) => a -> [a]
primesTo m = eratos [2..m] where
eratos [] = []
eratos (p:xs) = p : eratos (xs `minus` [p*p, p*p+p..m])
第二个也是一样,但是内部使用了一个无限列表:
primes2 :: (Ord a, Num a, Enum a) => a -> [a]
primes2 m = takeWhile (<= m) (eratos [2..]) where
eratos [] = []
eratos (p:xs) = p : eratos (xs `minus` [p*p, p*p+p..])
在这两种情况下,减号函数都是:
minus :: (Ord a) => [a] -> [a] -> [a]
minus (x:xs) (y:ys) = case (compare x y) of
LT -> x : minus xs (y:ys)
EQ -> minus xs ys
GT -> minus (x:xs) ys
minus xs _ = xs
后一种实现比前一种慢得多(~100 倍),我不明白为什么。我原以为 Haskell 的懒惰评估会使它们在底层相当等效。
出于问题的目的,这显然是一个简化的测试用例 - 在现实生活中优化不会有问题(虽然我不明白为什么需要它),但对我来说,一个只生成无限列表的函数of primes 比有限列表更普遍有用,但处理起来似乎更慢。
【问题讨论】:
-
请注意,您可以从 primes2 中提升 takeWhile,并尝试更通用的 primes 函数。您可以查看生成的 Core 和 C 以查看差异,但我认为简而言之,从算法上考虑,不同之处在于无限列表版本做了一些“重叠”工作,这使得 m+1 的工作量比 primesTo 少两次,对于 m,然后对于 m+1。但是,如果您不需要这项工作,最好不要这样做。所以对于这个算法,如果你提前知道你需要的最大 m,你可以节省工作。
-
寻找另一个具有相似属性的问题+算法作为练习会很有启发性。
标签: performance haskell