mam zamiar wykazać problem stosując następujący przykładowy programHaskell: niepotrzebne przewartościowań w stałych wyrażeniach
{-# 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))
Tutaj standardMap k
jest funkcja sparametryzowanego i k=(cos (pi/3))-(sin (pi/3))
jest parametrem. Jeśli skompiluję ten program z ghc -O3 -fllvm
, czas wykonania na moim komputerze wynosi około 42s
, jednak jeśli napiszę k
w postaci 0.5 - (sin (pi/3))
, czas wykonania będzie równy 21s
, a jeśli napiszę k = 0.5 - 0.5 * (sqrt 3)
, to zajmie to tylko 12s
.
Wniosek jest taki, że k
jest ponownie oceniany przy każdym wywołaniu standardMap k
.
Dlaczego to nie jest zoptymalizowane?
P.S. Kompilator ghc 7.6.3 na ArchLinux
EDIT
Dla tych, którzy obawiają się o dziwnych właściwościach standardMap
tu jest prostszy i bardziej intuicyjny przykład, który wykazuje ten sam problem
{-# 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)
EDYTOWANIE
Zanim zadałem pytanie, które próbowałem wykonać, zmieniono je na k
ja sposób Don zasugerował, ale z ghc -O3
nie widziałem różnicy. Rozwiązanie ze ścisłością działa, jeśli program jest skompilowany z ghc -O2
. Tęskniłem za tym, ponieważ nie wypróbowałem wszystkich możliwych kombinacji flag z wszystkimi możliwymi wersjami programu.
Jaka jest różnica między -O3
a -O2
, która wpływa na takie przypadki?
Czy powinienem ogólnie preferować -O2
?
EDIT
Jak zauważył Mike Hartl i innych, jeśli rotate k
zmienia się rotate $ k
lub standardMap k
do standardMap $ k
, wydajność poprawia się, choć nie jest to najlepszy możliwy (roztwór Dona). Czemu?
Czy można uzyskać podobne rezultaty ghc -O2 -fllvm lub ghc -O -fllvm? –
@jamesj Tak, jest jeszcze gorzej. Spodziewam się, że takie rzeczy zostaną zoptymalizowane w językach funkcjonalnych, nawet bez flag optymalizacyjnych. –
Powinieneś prawdopodobnie dodać wersje GHC i LLVM, których używasz. – Changaco