Jak inni już zauważyli, Haskell wymaga automatycznego , dynamicznego zarządzania pamięcią: automatyczne zarządzanie pamięcią jest konieczne, ponieważ ręczne zarządzanie pamięcią jest niebezpieczne; dynamiczne zarządzanie pamięcią jest konieczne, ponieważ w przypadku niektórych programów czas życia obiektu można określić tylko w czasie wykonywania.
Weźmy na przykład pod uwagę następujący program:
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
W tym programie lista [1..1000]
musi być przechowywana w pamięci, dopóki użytkownik nie wpisze „wyczyść”; więc czas życia tego musi być określany dynamicznie, i dlatego konieczne jest dynamiczne zarządzanie pamięcią.
W tym sensie automatyczna alokacja dynamicznej pamięci jest niezbędna, co w praktyce oznacza: tak , Haskell wymaga modułu odśmiecania pamięci, ponieważ czyszczenie pamięci jest automatycznym menedżerem pamięci dynamicznej o najwyższej wydajności.
Jednak...
Chociaż garbage collector jest konieczny, możemy spróbować znaleźć kilka specjalnych przypadków, w których kompilator może użyć tańszego schematu zarządzania pamięcią niż czyszczenie pamięci. Na przykład podane
f :: Integer -> Integer
f x = let x2 = x*x in x2*x2
możemy mieć nadzieję, że kompilator wykryje, że x2
można bezpiecznie cofnąć przydział przy zwrocie f
(zamiast czekać, aż moduł odśmiecania pamięci zwolni przydziałx2
). Zasadniczo prosimy, aby kompilator przeprowadził analizę ucieczki, aby przekonwertować alokacje na stertę zebraną bez pamięci na alokacje na stosie, jeśli to możliwe.
Nie jest to zbyt nierozsądne, aby poprosić o: kompilator jhc haskell robi to, chociaż GHC tego nie robi. Simon Marlow mówi, że generowany przez GHC garbage collector sprawia, że analiza ucieczki jest w większości niepotrzebna.
jhc w rzeczywistości wykorzystuje wyrafinowaną formę analizy ucieczki znaną jako wnioskowanie o regionie . Rozważać
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
W tym przypadku uproszczona analiza ucieczki zakończyłaby się wnioskiem, że x2
ucieka z f
(ponieważ jest zwracana w krotce), a zatem x2
musi zostać przydzielona na stercie zebranym z pamięci. Z drugiej strony wnioskowanie o regionie jest w stanie wykryć, że x2
można to cofnąć, kiedyg
zwrotu; chodzi tutaj o to, x2
aby alokować w g
regionie, a nie w f
regionie.
Poza Haskellem
Chociaż wnioskowanie o regionie jest pomocne w niektórych przypadkach, jak omówiono powyżej, wydaje się, że trudno jest go skutecznie pogodzić z leniwą oceną (patrz komentarze Edwarda Kmetta i Simona Peytona Jonesa ). Weźmy na przykład pod uwagę
f :: Integer -> Integer
f n = product [1..n]
Można by pokusić się o przydzielenie listy [1..n]
na stosie i zwolnienie jej po f
zwróceniu, ale byłoby to katastrofalne: zmieniłoby się f
z używania pamięci O (1) (w ramach czyszczenia pamięci) na pamięć O (n).
W latach 90. i na początku 2000 r. Przeprowadzono obszerne prace nad wnioskiem o regiony dla ścisłego języka funkcjonalnego ML. Mads Tofte, Lars Birkedal, Martin Elsman, Niels Hallenberg napisali dość czytelną retrospektywę na temat ich pracy nad wnioskiem o regiony, z których większość zintegrowali z kompilatorem MLKit . Eksperymentowali z zarządzaniem pamięcią opartym wyłącznie na regionach (tj. Bez modułu wyrzucania elementów bezużytecznych), a także z zarządzaniem pamięcią opartą na regionach / gromadzeniu elementów bezużytecznych i stwierdzili, że ich programy testowe działają „od 10 razy szybciej do 4 razy wolniej” niż czyste zebrane wersje.