为了测试教会编码列表如何针对用户定义列表和本地列表执行,我已经准备了3个基准:
data List a = Cons a (List a) | Nil deriving Show
lenumTil n = go n Nil where
go 0 result = result
go n result = go (n-1) (Cons (n-1) result)
lsum Nil = 0
lsum (Cons h t) = h + (lsum t)
main = print (lsum (lenumTil (100000000 :: Int)))
main = print $ sum ([0..100000000-1] :: [Int])
fsum = (\ a -> (a (+) 0))
fenumTil n cons nil = go n nil where
go 0 result = result
go n result = go (n-1) (cons (n-1) result)
main = print $ (fsum (fenumTil (100000000 :: Int)) :: Int)
基准测试结果出乎意料:
-- 4999999950000000
-- real 0m22.520s
-- user 0m59.815s
-- sys 0m20.327s
-- 4999999950000000
-- real 0m0.999s
-- user 0m1.357s
-- sys 0m0.252s
-- 4999999950000000
-- real 0m0.010s
-- user 0m0.002s
-- sys 0m0.003s
人们可以期待,通过针对本地列表的大量特定优化,它们将表现最佳。然而,教堂名单的表现优于100倍因素,并且优于用户定义的ADT 2250倍因子。我用GHC -O2
编译了所有程序。我已尝试将sum
替换为foldl'
,结果相同。我尝试添加用户输入以确保教堂列表版本没有针对常量进行优化。 arkeet
在#haskell上指出,通过检查Core,本机版本有一个中间列表,但为什么呢?使用额外的reverse
强制分配,所有3个执行大致相同。
答案 0 :(得分:19)
GHC 7.10进行了call arity分析,我们可以根据foldl
定义foldr
,从而让左侧折叠(包括sum
)参与融合。 GHC 7.8还将sum
与foldl
定义,但它不能将列表融合在一起。因此,GHC 7.10与教会版本的表现最佳且相同。
教会版本是儿童游戏,可以在GHC版本中进行优化。我们只需要将(+)
和0
内联到fenumTil
,然后我们就会有一个明显的尾递归go
,它可以很容易地取消装箱,然后通过代码生成器。
用户定义的版本不是尾递归的,它在线性空间中工作,这当然会破坏性能。