GHC speichert keine Funktionen.
Es berechnet jedoch einen bestimmten Ausdruck im Code höchstens einmal pro Mal, wenn sein umgebender Lambda-Ausdruck eingegeben wird, oder höchstens einmal, wenn er sich auf der obersten Ebene befindet. Das Bestimmen, wo sich die Lambda-Ausdrücke befinden, kann etwas schwierig sein, wenn Sie syntaktischen Zucker wie in Ihrem Beispiel verwenden. Konvertieren Sie diese also in eine äquivalente desugarierte Syntax:
m1' = (!!) (filter odd [1..]) -- NB: See below!
m2' = \n -> (!!) (filter odd [1..]) n
(Hinweis: Der Haskell 98-Bericht beschreibt tatsächlich einen linken Bedienerabschnitt (a %)
als äquivalent zu \b -> (%) a b
, aber GHC empfiehlt ihn (%) a
. Diese sind technisch unterschiedlich, da sie durch unterschieden werden können seq
. Ich glaube, ich habe möglicherweise ein GHC Trac-Ticket dazu eingereicht.)
In Anbetracht dieser, können Sie diese in sehen m1'
, der Ausdruck filter odd [1..]
ist nicht in jedem Lambda-Ausdruck enthalten ist , so wird es nur einmal pro Durchlauf des Programms berechnet werden, während in m2'
, filter odd [1..]
wird jedes Mal , wenn der Lambda-Ausdruck berechnet werden eingegeben wird , das heißt, bei jedem Anruf von m2'
. Das erklärt den Unterschied im Timing, den Sie sehen.
Tatsächlich teilen einige Versionen von GHC mit bestimmten Optimierungsoptionen mehr Werte als in der obigen Beschreibung angegeben. Dies kann in einigen Situationen problematisch sein. Betrachten Sie zum Beispiel die Funktion
f = \x -> let y = [1..30000000] in foldl' (+) 0 (y ++ [x])
GHC stellt möglicherweise fest, dass y
dies nicht von x
der Funktion abhängt, und schreibt sie neu in
f = let y = [1..30000000] in \x -> foldl' (+) 0 (y ++ [x])
In diesem Fall ist die neue Version viel weniger effizient, da sie etwa 1 GB aus dem Speicher lesen muss, in dem sie y
gespeichert ist, während die ursprüngliche Version auf konstantem Speicherplatz ausgeführt wird und in den Cache des Prozessors passt. Unter GHC 6.12.1 ist die Funktion f
beim Kompilieren ohne Optimierungen fast doppelt so schnell wie beim Kompilieren -O2
.
seq
m1 10000000). Es gibt jedoch einen Unterschied, wenn kein Optimierungsflag angegeben ist. Und beide Varianten Ihres "f" haben übrigens unabhängig von der Optimierung eine maximale Residenz von 5356 Bytes (mit weniger Gesamtzuordnung, wenn -O2 verwendet wird).