GHC speichert keine Funktionen.
Es berechnet jedoch jeden gegebenen Ausdruck im Code höchstens einmal pro Zeit, dass sein umgebender Lambda-Ausdruck eingegeben wird, oder höchstens einmal, wenn es auf oberster Ebene ist. Bestimmen, wo die Lambda-Ausdrücke sind, kann ein wenig schwierig sein, wenn Sie syntaktischen Zucker verwenden wie in Ihrem Beispiel, also lassen Sie sich wandeln diese in äquivalenter entzuckert Syntax:
m1' = (!!) (filter odd [1..]) -- NB: See below!
m2' = \n -> (!!) (filter odd [1..]) n
(Hinweis: Der Bericht Haskell 98 beschreibt tatsächlich einen linken Operator Abschnitt wie (a %)
gleich \b -> (%) a b
, aber GHC desugars es (%) a
. diese technisch unterschiedlich sind, weil sie von seq
unterschieden werden können. ich glaube, ich könnte eine GHC Trac Ticket über diese Erklärungen abgegeben haben.)
dies gegeben, können Sie sehe, dass in m1'
der Ausdruck filter odd [1..]
nicht enthalten ist i n jeder Lambda-Ausdruck, so wird er nur einmal pro Lauf des Programms berechnet, während in m2'
, jedes Mal berechnet wird, wenn der Lambda-Ausdruck eingegeben wird, d. h. bei jedem Aufruf von m2'
. Das erklärt den Zeitunterschied, den Sie sehen.
Tatsächlich teilen einige Versionen von GHC mit bestimmten Optimierungsoptionen mehr Werte als die obige Beschreibung angibt. Dies kann in manchen Situationen problematisch sein. Betrachten wir zum Beispiel die Funktion
f = \x -> let y = [1..30000000] in foldl' (+) 0 (y ++ [x])
GHC könnte feststellen, dass y
nicht auf x
abhängt und schreiben Sie die Funktion
f = let y = [1..30000000] in \x -> foldl' (+) 0 (y ++ [x])
In diesem Fall ist die neue Version viel weniger effizient, weil es haben um ungefähr 1 GB aus dem Speicher zu lesen, wo y
gespeichert ist, während die ursprüngliche Version in konstantem Raum laufen und in den Cache des Prozessors passen würde. Tatsächlich ist die Funktion f
unter GHC 6.12.1 fast doppelt so schnell, wenn ohne Optimierungen kompiliert wird, als es mit -O2
kompiliert wird.
Die Kosten zu bewerten (Filter ungerade [1 ..]) Ausdruck ist sowieso nahe Null - es ist schließlich faul Liste, so dass die tatsächlichen Kosten in (x !! 10000000) Anwendung ist, wenn die Liste tatsächlich ist bewertet. Außerdem scheinen sowohl m1 als auch m2 nur einmal mit -O2 und -O1 (auf meinem GHC 6.12.3) mindestens innerhalb des folgenden Tests ausgewertet zu werden: (test = m1 10000000 'seq' m1 10000000). Es gibt jedoch einen Unterschied, wenn kein Optimierungs-Flag angegeben ist. Und beide Varianten Ihres "f" haben übrigens unabhängig von der Optimierung eine maximale Residenz von 5356 Bytes (mit weniger Gesamtbelegung, wenn -O2 verwendet wird). –
@ Ed'ka: Versuchen Sie dieses Testprogramm mit der obigen Definition von 'f':' main = interact $ unlines. (Zeigen. Karte f. lesen). Linien "; kompilieren mit oder ohne '-O2'; dann echo 1 | ./main'. Wenn Sie einen Test wie 'main = print (f 5)' schreiben, kann 'y' bei der Verwendung als Garbage-Collection verwendet werden und es gibt keinen Unterschied zwischen den beiden' f's. –
äh, das sollte 'map (show. F. Lesen)' sein, natürlich. Und jetzt, nachdem ich GHC 6.12.3 heruntergeladen habe, sehe ich dieselben Ergebnisse wie in GHC 6.12.1. Und ja, Sie haben recht mit den ursprünglichen 'm1' und' m2': Versionen von GHC, die diese Art des Hebens mit aktivierten Optimierungen durchführen, verwandeln 'm2' in' m1'. –