2012-02-17 80 views
8

我试图了解在runhaskell下运行程序时观察到的性能异常。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下编译时不会发生这种情况。

+1

我的猜测是,由于runhaskell只进行少量的优化,所以第二个会遇到某些严格问题。 – fuz 2012-02-17 08:52:45

回答

9

尽管函数应该是相同的,但它们在应用方式上有所不同。在第一个定义中,isFactor已完全应用于呼叫站点isFactor x。在第二个定义中,它不是,因为现在isFactor明确地带有两个参数。

即使极小的优化也足以让GHC看穿这个并为两者创建相同的代码,但是如果使用-O0 -ddump-simpl进行编译,则可以确定没有优化就会产生差异(至少在ghc-7.2.1 ,YMMV与其他版本)。

随着第一个isFactor,GHC创建一个函数作为谓词传递给“GHC.List.Filter”,并调用mod 10000000(==)内联。对于第二种定义,相反,isFactor中的大多数调用都是对类函数的容限引用,并且在多个调用isFactor之间不共享。所以有很多字典开销是完全没有必要的。

这几乎从来都不是问题,因为即使默认的编译器设置也会优化它,但runhaskell显然甚至没有那么多。即使如此,我偶尔也会编码为someFun x y = \z ->,因为我知道someFun将部分应用,并且这是保持呼叫之间共享的唯一方式(即GHC的优化器不够聪明)。

5

据我所知,runhaskell几乎没有优化。它旨在快速加载和运行代码。如果它做了更多的优化,代码开始运行需要更长的时间。当然,在这种情况下,代码运行得更快,优化。

据我所知,如果编译版本的代码存在,那么runhaskell将使用它。因此,如果性能对您来说很重要,那么请确保先编译优化,然后再开启。 (我认为你甚至可以将开关转到runhaskell来打开优化 - 你必须检查文档...)

+0

感谢您的回答!我知道runhaskell并没有执行很多优化,但在我的脑海中,我认为'foo x = bar x'和'foo = bar'应该生成完全相同的代码。这就是我的困惑所在。即使没有优化,我只是想明白为什么这两个会有不同的表现。 – Steve 2012-02-17 09:38:55

+1

我可以想象,根据你定义函数的方式,thunk的结构究竟有些微小的差异。类似于一个版本的东西与所有调用共享一个thunk,而另一个版本为每个调用创建一个副本,导致发生更多的分配和释放。无论如何,这将是我的_guess_;你必须向GHC开发者询问“真正”的原因。显然,在优化开启的情况下,两种方式最终都会产生相同的代码。只是没有优化通过,那不会发生。 – MathematicalOrchid 2012-02-17 09:45:20

+0

ghci和runhaskell AFAIK都关闭所有优化,因为解释器不支持取消装箱值。 – fuz 2012-02-17 10:32:43