这是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)
我只想询问任何有关此问题的见解。更详细的将得到投票。请记住,我是函数式编程的新手,甚至关于尾递归与常规递归的简单评论也会受到赞赏和赞成。
答案 0 :(得分:9)
我不知道你是怎么运行的,但我怀疑完整的清单是:
您似乎没有使用优化。请务必使用-O2
并在编译时尝试-fllvm
。新时间: 1m2.412s
使用显式类型签名并尽可能使用Int
(默认值为Integer
)。新时间: 0m15.486s
所以我们通过使用优化获得了近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
。