Haskell计算Ackermann 4 1的速度很慢?

时间:2013-12-11 21:55:45

标签: haskell ghc ackermann

这是7个月前的一个老问题,当时堆栈溢出器同意Haskell计算Ackermann函数的低效率是由于编译器错误造成的。

Ackermann very inefficient with Haskell/GHC

7个月后,这似乎是固定的。似乎ack使用线性内存运行,但它运行速度非常慢。

main = print (ack 4 1)
-- Ackermann function
ack 0 n = n + 1
ack m 0 = ack (m-1) 1
ack m n = ack (m-1) (ack m (n - 1))

$ time ./ack
65533

>real   8m53.274s
>user   8m47.313s
>sys    0m4.868s


Processor  2.8 GHz Intel Core i7
Memory  8 GB 1333 MHz DDR3
Software  Mac OS X Lion 10.7.5 (11G63)

我只想询问任何有关此问题的见解。更详细的将得到投票。请记住,我是函数式编程的新手,甚至关于尾递归与常规递归的简单评论也会受到赞赏和赞成。

2 个答案:

答案 0 :(得分:9)

我不知道你是怎么运行的,但我怀疑完整的清单是:

  1. 您的程序没有任何更改,编译时没有优化。初始时间: 7m29.755s
  2. 您似乎没有使用优化。请务必使用-O2并在编译时尝试-fllvm。新时间: 1m2.412s

  3. 使用显式类型签名并尽可能使用Int(默认值为Integer)。新时间: 0m15.486s

  4. 所以我们通过使用优化获得了近8倍的加速(为什么每个其他基准测试问题都没有使用优化标志?!?!?)而使用Int而不是Integer需要额外的~4倍

答案 1 :(得分:2)

ack添加类型签名:

ack :: Int -> Int -> Int

这可以解决您的代码的两个问题:

过于笼统的类型

如果没有签名,编译器将派生以下类型:

ack :: (Eq a, Eq b, Num a, Num b) => a -> b -> b

ack最终会推广到所有数字类型,而不仅仅是整数。这个额外的间接层使代码变慢。

赋予ack具体类型(如Int)会消除此间接性。

键入默认

另外,我猜你的主要动作是这样写的:

main = print (ack 4 1)

您的ack适用于任何数字类型,但您没有准确指定哪一种。这意味着GHC在一个名为 type defaulting 的过程中自动选择一个。

在这种情况下,它选择Integer,一种可变长度类型。由于Integer可以处理任意大小的数字,因此它比机器大小Int要慢得多。

结论

总结:

  • 始终为顶级定义编写类型签名。
  • 始终使用-Wall
  • 进行编译