2014-02-24 67 views
-2

在我开始之前,我想说我已经知道在绝大多数用例中,性能并不是一些“全部结束”或不相关的。我也知道“如果性能是一个巨大的问题,使用填空(C,汇编程序等)。”所以我不需要包含我刚才陈述的答案。假设我们在这里的目的是要么1)我只是在智力上好奇,或者2)我有其他相关的理由来探索这个。无论如何,虽然我并不是新的函数式编程(Erlang等),还是使用递归和单赋值(Prolog)编程,但我对Haskell来说是非常新的。我只是试图获得一些基本的基准测试,了解它如何执行基本任务,例如反复调用函数,遍历列表等。基本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的版本仍然较快,在任何情况下,我不知道我是否做了足够严格,以有足够的效果,是否还可以做得更多,我是否找错了树,还有一些其他的问题,等等。

基于一切我读过这么多年来,我相信哈斯克尔编译对于大多数“通用任务”,通常应该比Erlang更快,但是,进入并发的次数越多,不会越多。任何人都可以看到这些结果吗? “棚光”可以重写我的程序,使用不同的编译标志,解释一些事情等。

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

count :: Int -> Int 

这把哈斯克尔版本的性能就在二郎版本的。我做的第二件事是删除一个加法:

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

引起哈斯克尔版本打败二郎神版本(为了公平我调整了二郎版本还);但是我不知道为什么消除一个加法会产生这种效果,因为我不相信Erlang是一个数学计算野兽。我还想知道Haskell的编译器是否与其懒惰相结合,只是绕过所有函数调用并直接跳到答案。

+3

注意您在'print $!'中强制执行严格规定! ......没有任何意义,因为为了印刷,必须先评估一些东西。 – Sarah

回答

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优化您的第二个版本只返回一个常量,但我没有考虑这一点。在尾递归本身将作出显著的性能差异,虽然)。

+0

我想我不知道在Haskell中看到什么样的尾递归。程序的第一个版本应该是Erlang和我曾经工作的其他语言(Prolog等)的尾递归。我必须承认我不明白你写的Haskell代码,因此我有更多的研究要做。谢谢! – user1992634

+3

我真的不明白你的第一个版本是如何以任何语言递归的。一个函数是尾递归的,如果它所做的最后一件事就是调用它自己。您的版本在调用自己之后进行添加。 – fjh

+0

哎呀 - 你完全正确。应该可能改为如下所示:count(1 + x-1) – user1992634