Я собираюсь продемонстрировать проблему, используя следующую примерную программу
{-# LANGUAGE BangPatterns #-}
data Point = Point !Double !Double
fmod :: Double -> Double -> Double
fmod a b | a < 0 = b - fmod (abs a) b
| otherwise = if a < b then a
else let q = a / b
in b * (q - fromIntegral (floor q :: Int))
standardMap :: Double -> Point -> Point
standardMap k (Point q p) =
Point (fmod (q + p) (2 * pi)) (fmod (p + k * sin(q)) (2 * pi))
iterate' gen !p = p : (iterate' gen $ gen p)
main = putStrLn
. show
. (\(Point a b) -> a + b)
. head . drop 100000000
. iterate' (standardMap k) $ (Point 0.15 0.25)
where k = (cos (pi/3)) - (sin (pi/3))
Здесь standardMap k
- параметризованная функция, а k=(cos (pi/3))-(sin (pi/3))
- параметр. Если я скомпилирую эту программу с помощью ghc -O3 -fllvm
, время выполнения на моей машине примерно равно 42s
, однако, если я пишу k
в форме 0.5 - (sin (pi/3))
, время выполнения равно 21s
, и если я напишу k = 0.5 - 0.5 * (sqrt 3)
, будет принимать только 12s
.
Вывод состоит в том, что k
переоценивается при каждом вызове standardMap k
.
Почему это не оптимизировано?
P.S. компилятор ghc 7.6.3 на archlinux
ИЗМЕНИТЬ
Для тех, кто связан со странными свойствами standardMap
, здесь представлен более простой и интуитивно понятный пример, который показывает ту же проблему
{-# LANGUAGE BangPatterns #-}
data Point = Point !Double !Double
rotate :: Double -> Point -> Point
rotate k (Point q p) =
Point ((cos k) * q - (sin k) * p) ((sin k) * q + (cos k) * p)
iterate' gen !p = p : (iterate' gen $ gen p)
main = putStrLn
. show
. (\(Point a b) -> a + b)
. head . drop 100000000
. iterate' (rotate k) $ (Point 0.15 0.25)
where --k = (cos (pi/3)) - (sin (pi/3))
k = 0.5 - 0.5 * (sqrt 3)
EDIT
Прежде чем я задал вопрос, я попытался сделать k
strict, так же, как предложил Дон, но с ghc -O3
я не видел разницы. Решение со строгостью работает, если программа скомпилирована с помощью ghc -O2
. Я пропустил это, потому что я не пытался использовать все возможные комбинации флагов со всеми возможными версиями программы.
В чем разница между -O3
и -O2
, которая влияет на такие случаи?
Должен ли я предпочитать -O2
вообще?
EDIT
Как заметил Майк Хартл и другие, если rotate k
изменено на rotate $ k
или standardMap k
на standardMap $ k
, производительность улучшится, хотя это не самое лучшее (решение Don). Почему?