Wie andere bereits betont haben, erfordert Haskell eine automatische , dynamische Speicherverwaltung: Eine automatische Speicherverwaltung ist erforderlich, da die manuelle Speicherverwaltung nicht sicher ist. Eine dynamische Speicherverwaltung ist erforderlich, da bei einigen Programmen die Lebensdauer eines Objekts nur zur Laufzeit bestimmt werden kann.
Betrachten Sie beispielsweise das folgende Programm:
main = loop (Just [1..1000]) where
loop :: Maybe [Int] -> IO ()
loop obj = do
print obj
resp <- getLine
if resp == "clear"
then loop Nothing
else loop obj
In diesem Programm muss die Liste [1..1000]
gespeichert bleiben, bis der Benutzer "clear" eingibt. Daher muss die Lebensdauer dynamisch bestimmt werden. Aus diesem Grund ist eine dynamische Speicherverwaltung erforderlich.
In diesem Sinne ist eine automatisierte dynamische Speicherzuweisung erforderlich. In der Praxis bedeutet dies: Ja , Haskell benötigt einen Garbage Collector, da die Garbage Collection der leistungsstärkste automatische dynamische Speichermanager ist.
Jedoch...
Obwohl ein Garbage Collector erforderlich ist, versuchen wir möglicherweise, einige Sonderfälle zu finden, in denen der Compiler ein billigeres Speicherverwaltungsschema als die Garbage Collection verwenden kann. Zum Beispiel gegeben
f :: Integer -> Integer
f x = let x2 = x*x in x2*x2
Wir können hoffen, dass der Compiler x2
erkennt, dass die Zuordnung bei der f
Rückgabe sicher aufgehoben werden kann (anstatt darauf zu warten, dass der Garbage Collector die Zuordnung aufhebt x2
). Im Wesentlichen bitten wir den Compiler, eine Escape-Analyse durchzuführen , um Zuweisungen in Zuordnungen auf dem Stapel zu konvertieren , wo immer dies möglich ist.
Dies ist nicht zu unangemessen, um danach zu fragen: Der jhc-Hashkell-Compiler tut dies, GHC jedoch nicht. Simon Marlow sagt, dass der Garbage Collector von GHC die Fluchtanalyse größtenteils unnötig macht.
jhc verwendet tatsächlich eine ausgeklügelte Form der Fluchtanalyse, die als Regionsinferenz bekannt ist . Erwägen
f :: Integer -> (Integer, Integer)
f x = let x2 = x * x in (x2, x2+1)
g :: Integer -> Integer
g x = case f x of (y, z) -> y + z
In diesem Fall würde eine vereinfachte Escape-Analyse ergeben, dass Escape x2
von f
(weil es im Tupel zurückgegeben wird) und daher x2
auf dem durch Müll gesammelten Heap zugewiesen werden muss. Die Regionsinferenz hingegen kann erkennen, dass x2
die Zuordnung bei der g
Rückkehr aufgehoben werden kann . Die Idee hier ist, dass x2
die Zuordnung eher in g
der Region als in f
der Region erfolgen sollte.
Jenseits von Haskell
Während die Inferenz von Regionen in bestimmten Fällen hilfreich ist, wie oben erläutert, scheint es schwierig zu sein, sie effektiv mit einer verzögerten Bewertung in Einklang zu bringen (siehe die Kommentare von Edward Kmett und Simon Peyton Jones ). Betrachten Sie zum Beispiel
f :: Integer -> Integer
f n = product [1..n]
Man könnte versucht sein, die Liste [1..n]
auf dem Stapel zuzuweisen und sie nach der f
Rückgabe freizugeben , aber dies wäre katastrophal: Sie würde sich f
von der Verwendung von O (1) -Speicher (unter Garbage Collection) zu O (n) -Speicher ändern .
In den 1990er und frühen 2000er Jahren wurden umfangreiche Arbeiten zur regionalen Inferenz für die strenge funktionale Sprache ML durchgeführt. Mads Tofte, Lars Birkedal, Martin Elsman und Niels Hallenberg haben eine gut lesbare Retrospektive über ihre Arbeit zur Regionsinferenz geschrieben , von der sie einen Großteil in den MLKit-Compiler integriert haben . Sie experimentierten mit einer rein region-basierten Speicherverwaltung (dh ohne Garbage Collector) sowie einer hybriden region-basierten / Garbage-Collected-Speicherverwaltung und berichteten, dass ihre Testprogramme "zwischen 10-mal schneller und 4-mal langsamer" liefen als reiner Garbage- gesammelte Versionen.