переменных “развернуть” значение. Примитивный конструктор не считается, поскольку он сохранён гло-
бально, в итоге получилось 10 слов. Посмотрим на ещё один пример, распишем значение [ Just True, Just
True, Nothing]:
nil
= []
true
= True
nothing = Nothing
letx1 = Justtrue
-- 2 слова
x2 = Justtrue
-- 2 слова
p1 = Consnothing nil
-- 3 слова
p2 = Consx2 p1
-- 3 слова
p3 = Consx1 p2
-- 3 слова
in
p3
----------
-- 13 слов
Обычно одно слово соответствует 16, 32 или 64 битам. Эта цифра зависит от процессора. Мы считали,
что любое значение можно поместить в одно слово, но это не так. Возьмём к примеру действительные чис-
ла с двойной точностью, они не поместятся в одно слово. Это необходимо учитывать при оценке объёма
занимаемой памяти.
10.5 Управление памятью. Сборщик мусора
В прошлом разделе для простоты мы считали, что объекты только добавляются в кучу. На самом деле это
не так. Допустим во время вычисления функции нам нужно было вычислить какие-то промежуточные дан-
ные, например объявленные в локальных переменных, тогда после вычисления результата все эти значения
больше не нужны. При этом в куче висит много-много объектов, которые уже не нужны. Нам нужно как-то от
них избавится. Этой задачей занимается отдельный блок вычислителя, который называется сборщиком му-
сора (garbage collector), соответственно процесс автоматического освобождения памяти называется сборкой
мусора (garbage collection или GC).
На данный момент в GHC используется копирующий последовательный сборщик мусора, который рабо-
тает по алгоритму Чейни (Cheney). Для начала рассмотрим простой алгоритм сборки мусора. Мы выделяем
небольшой объём памяти и начинаем наполнять его объектами. Как только место кончится мы найдём все
“живые” объекты, а остальное пространство памяти будем считать свободным. Как только после очередной
очистки оказалось, что нам всё же не хватает места. Мы найдём все живые объекты, подсчитаем сколько ме-
ста они занимают и запросим у системы этот объём памяти. Скопируем все живые объекты на новое место, а
старую память будем считать свободной. Так например, если у нас было выделено 30 Мб памяти и оказалось,
что живые объекты занимают 10 Мб, мы выделим ещё 10 Мб, скопируем туда все живые объекты и общий
объём памяти станет равным 40 Мб.
Мы можем оптимизировать сборку мусора. Есть такая гипотеза, что большинство объектов имеют очень
короткую жизнь. Это промежуточные данные, локальные переменные. Нам нужен лишь результат функции,
но на подходе к результату мы сгенерируем много разовой информации. Ускорить очистку можно так. Мы
выделим совсем небольшой участок памяти внутри нашей кучи, его принято называть яслями (nursery area),
и будем выделять и собирать новые объекты только в нём, как только этот участок заполнится мы скопируем
все живые объекты из яслей в остальную память и снова будем наполнять ясли. Как только вся память закон-
чится мы поступим так же как и в предыдущем сценарии. Когда заканчивается место в яслях, мы проводим
поверхностную очистку (minor GC), а когда заканчивается вся память в текущей куче, мы проводим глубокую
очистку (major GC). Эта схема соответствует сборке с двумя поколениями.
10.6 Статистика выполнения программы
Процесс управления памятью скрыт от программиста. Но при этом в GHC есть развитые средства косвен-
ной диагностики работы программы. Пока мы пользовались самым простым способом проверки. Мы вклю-
чали флаг s в интерпретаторе. Пришло время познакомиться и с другими.
Управление памятью. Сборщик мусора | 163
Статистика вычислителя
Для начала научимся смотреть статистику работы вычислителя. Посмотреть статистику можно с помо-
щью флагов s[file] и S[file]. Эти флаги предназначены для вычислителя низкого уровня (realtime system
или RTS, далее просто вычислитель), они заключаются в окружение +RTS ... -RTS, если флаги идут в кон-
це строки и считается, что все последующие флаги предназначены для RTSмы можем просто написать ghc
–make file.hs +RTS ... Например скомпилируем такую программу:
module Main where
main =print $sum [1 ..1e5]
Теперь скомпилируем:
$ ghc --make sum.hs -rtsopts -fforce-recomp
Флаг rtsopts позволяет передавать скомпилированной программе флаги для вычислителя низкого уров-
Читать дальше