【问题标题】:Why is this recursive function in Haskell so slow?为什么 Haskell 中的递归函数这么慢?
【发布时间】:2011-10-31 11:43:55
【问题描述】:

我试图模仿 Sieve 使用 Haskell 找到所有小于某个数字的素数。我发现其他使用 Sieve 方法的 Haskell 程序速度很快。但是,我编写的以下递归函数非常慢。代码如下

sieve' :: Integer -> Integer -> [Integer]

sieve' n 1 = [2 .. n]

sieve' n (k + 1) | [x | x <- sieve' n k, x == k + 1] == [] = sieve' n k

    |otherwise = [x | x <- sieve' n k,  x == k + 1 || not (mod x (k + 1) == 0)]



sieve :: Integer -> [Integer]

sieve n = sieve' n n

筛 20 大约需要 2 分钟。在我写这个问题的时候,Sieve 30 还没有完成。

谁能解释为什么这个递归函数这么慢。感谢您的任何帮助,您可以提供。

【问题讨论】:

  • 作为 Haskell 中 Eratosthenes 筛子的最终权威,您应该看看 Melissa O'Neill 在函数式编程杂志 (2009) 上的文章 (lambda-the-ultimate.org/node/3127)。里面应该还有一些技巧。

标签: haskell


【解决方案1】:

sieve' 函数的第二个子句进行了两次递归调用 (sieve' n k),从而使您的算法在指数时间内执行。

要解决此问题,您可以将术语绑定到某个名称,从而确保对其进行一次评估:

sieve' n (k + 1) | [x | x <- rec, x == k + 1] == [] = rec
    |otherwise = [x | x <- rec,  x == k + 1 || not (mod x (k + 1) == 0)]
  where
    rec = sieve' n k

更新以响应询问编译器为何不自动执行此操作的评论:

这种称为 CSE(通用子表达式消除)的转换通常不是优化,而是时间和空间使用之间的权衡,因此最好留给程序员做决定。

谷歌搜索“CSE”揭示了一些有趣的讨论,其中一个参考 this very good example 来自 Simon Peyton Jones 1987 年出版的名为“函数式编程语言的实现”的书(天哪,这本书几乎和我一样古老)

【讨论】:

  • 谢谢,这正是问题所在。它现在运行得更快了。
  • 我觉得奇怪的是编译器错过了这个优化。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-11-12
  • 1970-01-01
  • 1970-01-01
  • 2013-08-02
  • 2017-01-10
  • 2015-02-26
  • 1970-01-01
相关资源
最近更新 更多