我试图了解在runhaskell下运行程序时观察到的性能异常。

有问题的程序是:

isFactor n = (0 ==) . (mod n)
factors x = filter (isFactor x) [2..x]
main = putStrLn $ show $ sum $ factors 10000000

当我运行此命令时,它需要1.18秒。

但是,如果我将isFactor重新定义为:
isFactor n f = (0 ==) (mod n f)

那么该程序将花费17.7秒。

那是性能上的巨大差异,我希望这些程序是等效的。有人知道我在这里想念的吗?

注意:在GHC下编译时不会发生这种情况。

最佳答案

尽管功能应该相同,但是它们的应用方式有所不同。根据第一个定义,isFactor完全应用在调用站点isFactor x上。在第二个定义中,它不是,因为现在isFactor显式接受两个参数。

即使最小的优化也足以使GHC看到并为两者创建相同的代码,但是,如果您使用-O0 -ddump-simpl进行编译,则可以确定不进行任何优化就可以有所作为(至少与ghc-7.2.1,YMMV与其他版本)。

使用第一个isFactor,GHC创建一个作为谓词传递给“GHC.List.Filter”的函数,并插入对mod 10000000(==)的调用。对于第二个定义,相反,发生的是isFactor中的大多数调用是对类函数的绑定(bind)绑定(bind)引用,并且在isFactor的多次调用之间不共享。因此,有很多词典开销是完全不必要的。

这几乎从来都不是问题,因为即使是默认的编译器设置也可以对其进行优化,但是runhaskell显然没有做太多事情。即使这样,我还是偶尔将代码结构化为someFun x y = \z ->,因为我知道someFun将被部分应用,这是保留调用之间共享的唯一方法(即GHC的优化器不够聪明)。

10-08 12:48