【问题标题】:Slower execution when using an infinite list使用无限列表时执行速度较慢
【发布时间】: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


【解决方案1】:

在我看来,两者之间存在很大差异

(xs `minus` [p*p, p*p+p..m])  -- primesTo
(xs `minus` [p*p, p*p+p..])   -- primes2

函数minus 逐对遍历列表,并在一个列表到达末尾时终止。在上面的第一个minus 表达式中,当后面的列表用尽时,这发生在不超过(m-p*p)/p 步骤中。在第二个中,它总是按照length xs的顺序执行步骤。

因此,您的无限列表至少禁用了一项有意义的优化。

【讨论】:

  • 我知道后一个列表终止了减号的执行,并且随着 p 趋向于 m,减号计算变得更短。我想了解的是惰性评估在何时何地适用和不适用于无限列表 - 我期望在这种情况下,减号只会被评估为满足 takeWhile 所需的量(以及上面的所有内容) it) 这与显式有界的情况没有太大的不同。
  • minus 不会成对遍历列表。只有当人头相等时,minus 才会从两个列表中消费。因此,在这两种情况下,步数都将更接近length xs。但是,在第一个中,您可以用尽素数列表,避免在p 的最后一个倍数和下一个倍数之间检查xs
【解决方案2】:

一个区别是,在第二种情况下,您需要生成一个额外的素数。您需要在takeWhile 知道它的停止时间之前生成大于m 的第一个素数。

此外,要过滤的列表和倍数列表上的[..m] 边界有助于减少计算次数。每当这些列表中的一个为空时,minus 立即通过其 secons 子句返回,而在无限情况下,减号卡在第一种情况下。如果您还测试只有一个列表是无限的情况,您可以更好地探索这一点:

--this is also slow
primes3 :: (Ord a, Num a, Enum a) => a -> [a]
primes3 m = takeWhile (<= m) (eratos [2..m])  where
   eratos []     = []
   eratos (p:xs) = p : eratos (xs `minus` [p*p, p*p+p..])

--this fast
primes4 :: (Ord a, Num a, Enum a) => a -> [a]
primes4 m = takeWhile (<= m) (eratos [2..])  where
   eratos []     = []
   eratos (p:xs) = p : eratos (xs `minus` [p*p, p*p+p..m])

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-06-27
    • 2017-04-10
    • 1970-01-01
    • 2016-01-07
    • 1970-01-01
    • 1970-01-01
    • 2010-12-08
    • 1970-01-01
    相关资源
    最近更新 更多