免责声明:可能会有一些可疑的Haskell。
我已经实现了两个版本的Fibonacci,一个是线性和尾递归(包括一个带有变种的版本),另一个使用经典的懒惰列表方法:
{-# LANGUAGE BangPatterns #-}
-- Linear
fibLinear :: Int -> Integer
fibLinear 0 = 0
fibLinear 1 = 1
fibLinear x = fibLin' 2 1 2
where
fibLin' i y z | i == x = y
fibLin' i y z = fibLin' (i+1) z (z+y)
-- Linear banged
fibLinearBang :: Int -> Integer
fibLinearBang 0 = 0
fibLinearBang 1 = 1
fibLinearBang x = fibLin' 2 1 2
where
fibLin' (!i) (!y) (!z) | i == x = y
fibLin' (!i) (!y) (!z) = fibLin' (i+1) z (z+y)
-- Haskeller classic lazy
fibClassic :: Int -> Integer
fibClassic n = fib' !! n
where fib' = 0:1:zipWith (+) fib' (tail fib')
使用criterion对其进行基准测试(code)时,我得到了一些不直观的结果:{30}对fibLinear
采用443.0 ns,fibLinearBang
采用279.5 ns,而对于73.51 ns为fibClassic
。这很奇怪,因为我会认为fibLinear
& fibLinearBang
可以优化为循环或跳转等。
我唯一的猜测是,fib'
中的fibClassic
可能会被记忆为常量列表,尽管它位于函数定义中,以便后续的函数调用重用相同的列表进行查找。
有人能给我一个明确的基准测试结果吗?
对于那些感兴趣的人,我已将项目放在Github。
答案 0 :(得分:-1)
tl; dr:fib'
确实在调用中重复使用
感谢评论者,我了解到full-laziness
不是(仅仅)用于描述Haskell如何评估的术语,而是一个实际的编译器优化术语,默认情况下是一个,但可以禁用(使用-fno-full-laziness
GHC选项标志。
使用该标志重新运行基准测试,我们看到表格已转为:
fibLinearBang
259.8 ns fibClassic
738.4 ns 这个表现以及this guide on GHC compiler options是非常有说服力的证据,证明fib'
确实被转化为类似于顶层引用的东西,它在不同的调用中被重用(并且是一个列表,被记忆)。方法。
该指南和paper it links to describing let-lifting都承认放松可能会导致内存占用率增加,这对我来说有点可怕(不要使用fibClassic
写一个虚拟即服务把它暴露在互联网上...),但我可能会来到它...