GHC nie zapamiętuje funkcji.
Jednak wylicza dowolne wyrażenie w kodzie co najwyżej raz na raz, gdy zostało wprowadzone otaczające je wyrażenie lambda, lub co najwyżej raz, jeśli znajduje się na najwyższym poziomie. Ustalenie, gdzie są wyrażenia lambda, może być trochę trudne, gdy używasz cukru składniowego, jak w przykładzie, więc przekonwertujmy je na równoważną składnię pozbawioną cukru:
m1' = (!!) (filter odd [1..]) -- NB: See below!
m2' = \n -> (!!) (filter odd [1..]) n
(Uwaga: raport Haskell 98 faktycznie opisuje lewą sekcję operatora, podobną (a %)
do \b -> (%) a b
, ale GHC usuwa cukier (%) a
. Są one technicznie różne, ponieważ można je rozróżnić seq
. Myślę, że mogłem przesłać zgłoszenie GHC Trac na ten temat.)
Biorąc to pod uwagę, możesz zobaczyć, że w m1'
, wyrażenie filter odd [1..]
nie jest zawarte w żadnym wyrażeniu lambda, więc zostanie obliczone tylko raz na uruchomienie twojego programu, podczas gdy in m2'
, filter odd [1..]
zostanie obliczone za każdym razem, gdy zostanie wprowadzone wyrażenie lambda, tj. przy każdym wywołaniu m2'
. To wyjaśnia różnicę w czasie, który widzisz.
W rzeczywistości niektóre wersje GHC, z pewnymi opcjami optymalizacji, będą miały więcej wspólnych wartości, niż wskazuje powyższy opis. W niektórych sytuacjach może to być problematyczne. Na przykład rozważmy funkcję
f = \x -> let y = [1..30000000] in foldl' (+) 0 (y ++ [x])
GHC może zauważyć, że y
nie zależy od funkcji x
i nie przepisuje jej na
f = let y = [1..30000000] in \x -> foldl' (+) 0 (y ++ [x])
W tym przypadku nowa wersja jest znacznie mniej wydajna, ponieważ będzie musiała odczytać około 1 GB z pamięci, w której y
jest przechowywana, podczas gdy wersja oryginalna działałaby na stałej przestrzeni i mieściłaby się w pamięci podręcznej procesora. W rzeczywistości w GHC 6.12.1 funkcja f
jest prawie dwa razy szybsza, gdy jest kompilowana bez optymalizacji, niż jest kompilowana z -O2
.
seq
m1 10000000). Istnieje jednak różnica, gdy nie określono flagi optymalizacji. Nawiasem mówiąc, oba warianty twojego "f" mają maksymalną rezystancję 5356 bajtów niezależnie od optymalizacji (z mniejszą całkowitą alokacją, gdy używane jest -O2).