Я анализировал влияние предложений where
на производительность программ на Haskell.
В Haskell, Мастерство функционального программирования, Томпсон, глава 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]
дважды....
Эффект [exchange2] заключается в вычислении списка
[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]
нужно хранить в памяти, а это занимает много времени. Что вы думаете?
PS: я нашел возможно, связанный с этим вопрос, но не совсем.
list
, поэтому он фактически вычисляет его, сохраняет в памяти и читает из памяти. - person talex   schedule 29.04.2019let x = expensiveComputation in f x x
в общем случае может быть вредным, поскольку может привести к двойному вычислениюx
. Используяwhere
, у нас может быть аналогичная проблема. Я думаю, что GHC довольно консервативен в отношении встраивания в этом случае, поскольку это может привести к проблемам с производительностью (например, выполнение двух рекурсивных вызовов вместо одного может привести к экспоненциальному взрыву). - person chi   schedule 29.04.2019