为什么总和比在haskell中的foldl'慢?

时间:2016-04-28 10:10:01

标签: haskell ghc ghci fold

为什么ghc中的默认sum比其foldl'foldl的{​​{1}})等效慢约10倍?如果是这种情况,为什么不使用foldl'实现?

import Data.List
> foldl' (+) 0 [1..10^7]
50000005000000
(0.39 secs, 963,528,816 bytes)

> sum [1..10^7]
50000005000000
(4.13 secs, 1,695,569,176 bytes)

为了完整性,此处还有foldlfoldr的统计信息。

> foldl (+) 0 [1..10^7]
50000005000000
(4.02 secs, 1,695,828,752 bytes)

> foldr (+) 0 [1..10^7]
50000005000000
(3.78 secs, 1,698,386,648 bytes)

看起来sum是使用foldl实现的,因为它们的运行时类似。在ghc 7.10.2上测试。

2 个答案:

答案 0 :(得分:10)

sum函数是使用GHC中的foldl实现的:

-- | The 'sum' function computes the sum of a finite list of numbers.
sum                     :: (Num a) => [a] -> a
{-# INLINE sum #-}
sum                     =  foldl (+) 0

可以看出in the source

必须这样,因为它是规范in the Haskell report

理由很可能是对于列表的某些惰性元素类型,foldl是正确的做法。 (我个人认为foldl几乎总是错误的,只应使用foldl'。)

通过充分优化,GHC将内联该定义,将其专门化为手头的元素类型,注意循环是严格的,并在每次迭代中强制累加器的评估;正如@AndrásKovács所观察到的那样,有效地将其转变为foldl'

自GHC-7.10起,sum itselfFoldable类型类的方法,默认定义通过foldMap。但是,instance Foldable []会使用sum的上述定义来覆盖此内容。

答案 1 :(得分:0)

为了补充@Joachim Breitner的回答,我找到了这个blog post,这是一个非常有趣的读物(摘自reddit讨论,感谢@ZhekaKozlov的链接)。

  

当Haskell 1.0在24年前的这一天发布时,根本没有seq函数,所以除了以“经典”的方式定义foldl之外别无选择。

     

最后,经过多次讨论六年后,我们在Haskell 1.3中获得了seq函数。虽然实际上在Haskell 1.3 seq中是Eval类的一部分,但是你无法在任何地方使用它,例如在foldl中。在Haskell 1.3中,你必须定义foldl'类型:

foldl' :: Eval b => (b -> a -> b) -> b -> [a] -> b
  

Haskell 1.4和Haskell 98摆脱了seq的Eval类约束,但foldl没有改变。 Hugs和GHC以及其他实现添加了非标准的foldl'。

     

我怀疑人们认为这是一个兼容性和惯性问题。很容易添加一个非标准的折叠'但你不能这么容易地改变标准。

     

我怀疑如果我们从一开始就有seq,那么我们就可以使用它来定义foldl。

     

Haskell的前身语言之一Miranda在Haskell 1.0之前已经有5年了。

顺便说一句,我已经设法通过使用

来减少20毫秒
foldl1' (+) [1..10^7]

所以,我猜foldl1'应该是sumproduct的默认值(特殊处理空列表)。