我正在分析
在Haskell函数式编程艺术, Thomspson的第20.4章中,我找到了以下示例:
多大的差异啊!为什么会这样呢?根据上面的引文,我以为
也许它之所以慢是因为
附注:我找到了一个可能相关的问题,但不完全相关。
where
子句对Haskell程序性能的影响。在Haskell函数式编程艺术, Thomspson的第20.4章中,我找到了以下示例:
exam1 :: Int -> [Int]
exam1 n = [1 .. n] ++ [1 .. n]
exam2 :: Int -> [Int]
exam2 n = list ++ list
where list = [1 .. n]
引用一下:
计算[exam1]所需的时间将为
O(n)
,所需的空间将为O(1)
,但我们将不得不计算表达式[1 .. n]
两次。...
[exam2]的效果是计算列表
[1 .. n]
一次,以便在计算后保存其值,以便能够再次使用它。...
如果我们通过引用
where
子句中的内容来节省空间,则必须支付其占用的空间的代价。
因此,我疯狂地认为-O2
标志必须处理这个并为我选择最佳行为。 我使用Criterion分析这两个示例的时间复杂度。
import Criterion.Main
exam1 :: Int -> [Int]
exam1 n = [1 .. n] ++ [1 .. n]
exam2 :: Int -> [Int]
exam2 n = list ++ list
where list = [1 .. n]
m :: Int
m = 1000000
main :: IO ()
main = defaultMain [ bench "exam1" $ nf exam1 m
, bench "exam2" $ nf exam2 m
]
我使用-O2
编译,并发现:
benchmarking exam1
time 15.11 ms (15.03 ms .. 15.16 ms)
1.000 R² (1.000 R² .. 1.000 R²)
mean 15.11 ms (15.08 ms .. 15.14 ms)
std dev 83.20 μs (53.18 μs .. 122.6 μs)
benchmarking exam2
time 76.27 ms (72.84 ms .. 82.75 ms)
0.987 R² (0.963 R² .. 0.997 R²)
mean 74.79 ms (70.20 ms .. 77.70 ms)
std dev 6.204 ms (3.871 ms .. 9.233 ms)
variance introduced by outliers: 26% (moderately inflated)
多大的差异啊!为什么会这样呢?根据上面的引文,我以为
exam2
应该更快但内存效率低。但是事实上,它实际上要慢得多(可能更加内存效率低,但需要进行测试)。也许它之所以慢是因为
[1..1e6]
必须存储在内存中,这需要很长时间。你怎么看?附注:我找到了一个可能相关的问题,但不完全相关。
let x = expensiveComputation in f x x
可能会有害,因为它会导致x
被计算两次。使用where
也会出现类似的问题。我认为GHC在这种情况下非常保守地进行内联,因为它可能会导致性能灾难(例如,执行两个递归调用而不是一个可能会导致指数级的爆炸增长)。 - chi