I was analyzing the effect of where
clauses on performance of Haskell programs.
In Haskell, The craft of functional programming, Thomspson, chapter 20.4, I found the following example:
exam1 :: Int -> [Int]
exam1 n = [1 .. n] ++ [1 .. n]
exam2 :: Int -> [Int]
exam2 n = list ++ list
where list = [1 .. n]
and, I quote,
The time taken to calculate [exam1] will be
O(n)
, and the space used will beO(1)
, but we will have to calculate the expression[1 .. n]
twice....
The effect [of exam2] is to compute the list
[1 .. n]
once, so that we save its value after calculating it in order to be able to use it again....
If we save something by referring to it in a
where
clause, we have to pay the penalty of the space that it occupies.
So I go wild and think that the -O2
flag must handle this and choose the best behavior for me. I analyze the time-complexity of these two examples using 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
]
I compile with -O2
, and find:
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)
What a difference! Why would that be? I thought that exam2
should be faster but memory inefficient (according to the quote above). But no, it is actually much slower (and probably more memory inefficient but that needs to be tested).
Maybe it is slower because [1 .. 1e6]
has to be stored in memory, and this takes a lot of time. What do you think?
PS: I found a possibly related question, but not really.