基本的Haskell性能

时间:2014-02-24 12:35:01

标签: performance haskell erlang

在开始之前,我想说我已经知道,在绝大多数用例中,性能不是“完全全部”或不相关。我也知道“如果性能是一个巨大的问题,请使用FILL-IN-THE-BLANK(C,汇编程序等)。”所以我不需要包含我刚才所说的答案。在这里为我们的目的假设1)我只是在理智上好奇,或者2)我有其他一些相关的理由来探索这个。

无论如何,虽然我不熟悉函数式编程(Erlang等)或者使用递归和单一赋值(Prolog)编程,但我对Haskell来说还是个新手。我只是试图获得一些关于它如何执行基本任务的基本基准,例如一遍又一遍地调用函数,遍历列表等等。

尝试一次又一次地调用函数来测量它的执行情况(我想要一个实际上什么都没做的函数,即'no-op',但是无法弄清楚如何让Haskell执行这样的构造) ,我写了这个:

count 0 = 0
count x = 1 + count (x-1)

main = print count 100000000   -- tried various values for this integer.

我将它与这个Erlang程序进行了比较:

count(0) -> 0 ;
count(I) -> 1 + count(I-1)

令我惊讶的是,Erlang程序在两个程序的多次运行中运行得更快。实际上,它(至少在表面上)比这更糟糕,因为即使是遍历x元素列表的Erlang变体程序(相对于简单地调用函数x次),运行速度也比上面的Haskell版本快。另外,我使用的是Haskell代码的编译版本(ghc --make -O3 -rtsopts)与Erlang的字节码解释器(没有Hipe)。

我没有(并且仍然没有)了解Haskell知道从哪里开始,但我的第一个猜测是怀疑懒惰。在对一些在线文档进行快速回顾之后,我将主要内容更改为以下内容:

main = print $! count 100000000

它似乎加速了一些,但Erlang版本仍然更快,无论如何我不确定我是否做了足够的严格来获得足够的效果,是否可以做更多的事情,我是否在咆哮错误的树,还有其他一些问题,等等。

基于我多年来所阅读的所有内容,我认为对于大多数“通用任务”,编译的Haskell通常应该比Erlang更快,尽管这可能越来越不正确,你进入并发性的次数越多越好。任何人都可以对这些结果有所了解吗? “棚灯”可以重写我的程序,使用不同的编译标志,解释一些事情等等。

编辑:我做了两件事,这两件事都有加速Haskell程序的效果。我做的第一件事就是将这种类型的信息添加到函数中:

count :: Int -> Int

这使得Haskell版本的性能正好适用于Erlang版本。我做的第二件事就是删除一个补充:

count 0 = 0
count x = count (x-1)

这导致Haskell版本击败Erlang版本(为了公平我也调整了Erlang版本);但我确实想知道为什么消除加法会产生这种效果,因为我不相信Erlang因为是一些数学计算野兽而闻名。我也想知道Haskell的编译器是否与其懒惰相结合只是绕过所有的函数调用并直接跳到答案。

1 个答案:

答案 0 :(得分:4)

第一个版本的count和第二个版本(总是返回0的版本)之间的一个重要区别是后者是尾递归的。第一个函数的尾递归版本是

count' :: Int -> Int
count' n = go n 0 where
    go 0 !acc = acc
    go n !acc = go (n - 1) (acc + 1)

只需不到三分之一的时间。

函数签名在这里很重要,因为没有它,GHC将函数类型默认为Integer -> Integer,这意味着它必须处理任意精度的整数而不是Int s。

(完全有可能GHC优化你的第二个版本只返回一个常量,但我没有考虑到这一点。尾递归本身会产生显着的性能差异。)