【发布时间】:2013-04-20 02:13:35
【问题描述】:
我尝试计算 Ackermann(4,1),但不同语言/编译器之间的性能差异很大。下面是我的Core i7 3820QM, 16G, Ubuntu 12.10 64bit,
C:1.6s,gcc -O3(使用 gcc 4.7.2)
int ack(int m, int n) {
if (m == 0) return n+1;
if (n == 0) return ack(m-1, 1);
return ack(m-1, ack(m, n-1));
}
int main() {
printf("%d\n", ack(4,1));
return 0;
}
OCaml:3.6s,ocamlopt(使用 ocaml 3.12.1)
let rec ack = function
| 0,n -> n+1
| m,0 -> ack (m-1, 1)
| m,n -> ack (m-1, ack (m, n-1))
in print_int (ack (4, 1))
标准 ML:5.1 秒 mlton -codegen c -cc-opt -O3 (使用 mlton 20100608)
fun ack 0 n = n+1
| ack m 0 = ack (m-1) 1
| ack m n = ack (m-1) (ack m (n-1));
print (Int.toString (ack 4 1));
球拍:11.5s racket (使用球拍 v5.3.3)
(require racket/unsafe/ops)
(define + unsafe-fx+)
(define - unsafe-fx-)
(define (ack m n)
(cond
[(zero? m) (+ n 1)]
[(zero? n) (ack (- m 1) 1)]
[else (ack (- m 1) (ack m (- n 1)))]))
(time (ack 4 1))
Haskell:未完成,在 22 秒后被系统杀死 ghc -O2(使用 ghc 7.4.2)
Haskell:1.8 秒 ajhc (使用 ajhc 0.8.0.4)
main = print $ ack 4 1
where ack :: Int -> Int -> Int
ack 0 n = n+1
ack m 0 = ack (m-1) 1
ack m n = ack (m-1) (ack m (n-1))
Haskell 版本是唯一无法正确终止的版本,因为它占用了太多内存。它会冻结我的机器并在被杀死之前填充交换空间。 在不严重修改代码的情况下,我可以做些什么来改进它?
编辑:我很欣赏一些渐近智能的解决方案,但它们并不是我所要求的。这更多是关于查看编译器是否以相当有效的方式(堆栈、尾调用、拆箱等)处理某些模式,而不是计算 ackermann 函数。
编辑 2:正如一些回复所指出的,这似乎是 a bug in recent versions of GHC。我用AJHC 尝试相同的代码并获得更好的性能。
非常感谢:)
【问题讨论】:
-
对于任何尝试这样做的人来说,这里的问题是计算问题,而不是算法问题。对
ack(4,1)的非记忆调用导致深度为 65535 的递归,以及对ack的总共 2862984010 次调用。 -
ghc-core显示Ints 正确地转换为未装箱的Int#,所以这不是问题。 -
@scvalex 谢谢。我盲目地尝试了这个。
-
Frege 中的相同代码在 8.3 秒内运行,尽管需要给 JVM 一些额外的堆栈空间。硬件是 i3 CPU M 350 @ 2.27GHz
-
您好,替代方法,该函数使用 Int,并且 GHC -O2 已经将其拆箱为 Int#。所以不需要显式注释。谢谢。
标签: performance haskell optimization recursion benchmarking