haskell-notes

ной диагностики работы программы. Пока мы пользовались самым простым способом проверки. Мы вклю-

чали флаг 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 позволяет передавать скомпилированной программе флаги для вычислителя низкого уров-

ня, далее для краткости мы будем называть его просто вычислителем. С флагом fforcerecomp программа

будет каждый раз заново пересобираться. Теперь посмотрим на статистику выполнения программы (флаг

s[file], в этом примере мы перенаправляем выход в поток stderr):

$ ./sum +RTS -sstderr

5.00005e9

14,145,284 bytes allocated in the heap

11,110,432 bytes copied during GC

2,865,704 bytes maximum residency (3 sample(s))

460,248 bytes maximum slop

7 MB total memory in use (0 MB lost due to fragmentation)

Tot time (elapsed)

Avg pause

Max pause

Gen

0

21 colls,

0 par

0.00s

0.01s

0.0006s

0.0036s

Gen

1

3 colls,

0 par

0.01s

0.01s

0.0026s

0.0051s

INIT

time

0.00s

(

0.00s elapsed)

MUT

time

0.01s

(

0.01s elapsed)

GC

time

0.01s

(

0.02s elapsed)

EXIT

time

0.00s

(

0.00s elapsed)

Total

time

0.02s

(

0.03s elapsed)

%GC

time

60.0%

(69.5% elapsed)

Alloc rate

1,767,939,507 bytes per MUT second

Productivity

40.0% of total user, 26.0% of total elapsed

Был распечатан результат и отчёт о работе программы. Разберёмся с показателями:

bytes allocated in the heap

— число байтов выделенных в куче

— за всё время работы программы

bytes copied during GC

— число скопированных байтов

— за всё время работы программы

bytes maximum residency

— в каком объёме памяти работала программа

— в скобках указано число глубоких очисток

bytes maximum slop

— максимум потерь памяти из-за фрагментации

total memory in use

— сколько всего памяти было запрошено у ОС

Показатель bytes maximum residency измеряется только при глубокой очистке, поскольку новая память

выделяется именно в этот момент. Размер памяти выделенной в куче гораздо больше общего объёма памяти.

Так происходит потому, что этот показатель указывает на общее число памяти в куче за всё время работы

программы. Ведь мы переиспользуем не нужную нам память. По этому показателю можно судить о том,

сколько замыканий (объектов) было выделено в куче.

Следующие две строчки говорят о числе сборок мусора. Мы видим, что GC выполнил 21 поверхностную

очистку (поколение 0) и 3 глубоких (покколение 1). Дальше идут показатели скорости. INIT и EXIT – это

164 | Глава 10: Реализация Haskell в GHC

инициализация и завершение программы. MUT – это полезная нагрузка, время, которая наша программа тра-

тила на изменение (MUTation) значений. GC – время сборки мусора. Далее GHC сообщил нам о том, что мы

провели 60% времени в сборке мусора. Это очень плохо. Продуктивность программы очень низкая. Затратна

глубокая сборка мусора, поверхностная – это дело обычное. Теперь посмотрим на показатели строгой версии

этой программы:

module Main where

import Data.List(foldl’)

sum’ = foldl’ (+) 0

main = print $ sum’ [1 .. 1e5]

Скомпилируем:

$ ghc —make sumStrict.hs -rtsopts -fforce-recomp

Посмотрим на статистику:

$ ./sumStrict +RTS -sstderr

5.00005e9

10,474,128 bytes allocated in the heap

24,324 bytes copied during GC

27,072 bytes maximum residency (1 sample(s))

27,388 bytes maximum slop

1 MB total memory in use (0 MB lost due to fragmentation)

Tot time (elapsed)

Avg pause

Max pause

Gen

0

19 colls,

0 par

0.00s

0.00s

0.0000s

0.0000s

Gen

1

1 colls,

0 par

0.00s

0.00s

0.0001s

0.0001s

INIT

time

0.00s

(

0.00s elapsed)

MUT

time

0.01s

(

0.01s elapsed)

GC

time

0.00s

(

0.00s elapsed)

EXIT

time

0.00s

(

0.00s elapsed)

Total

time

0.01s

(

0.01s elapsed)

%GC

time

0.0%

(3.0% elapsed)

Alloc rate

1,309,266,000 bytes per MUT second

Productivity 100.0% of total user, 116.0% of total elapsed

Мы видим, что произошла лишь одна глубокая сборка. И это существенно сказалось на продуктивности.

Кромке того мы видим, что программа заняла лишь 27 Кб памяти, вместо 2 Мб как в прошлом случае. Теперь

давайте покрутим ручки у GC. В GHC можно устанавливать разные параметры сборки мусора с помощью

флагов. Все флаги можно посмотреть в документации GHC. Мы обратим внимание на несколько флагов.

Флаг H назначает минимальное значение для стартового объёма кучи. Флаг A назначает объём памяти для

яслей. По умолчанию размер яслей равен 512 Кб (эта цифра может измениться). Изменением этих параметров

мы можем отдалить сборку мусора. Чем дольше работает программа между сборками, тем выше вероятность

того, что многие объекты уже не нужны.

Давайте убедимся в том, что поверхностные очистки происходят очень быстро и совсем не тормозят

программу. Установим размер яслей на 32 Кб вместо 512 Кб как по умолчанию (размер пишется сразу за

флагом, за цифрой идёт k или m):

$ ./sumStrict +RTS -A32k -sstderr

Tot time (elapsed)

Avg pause

Max pause

Gen

0

318 colls,

0 par

0.00s

0.00s

0.0000s

0.0000s

Gen

1

1 colls,

0 par

0.00s

0.00s

0.0001s

0.0001s

MUT

time

0.01s

(

0.01s elapsed)

GC

time

0.00s

(

0.00s elapsed)

%GC

time

0.0%

(11.8% elapsed)

Статистика выполнения программы | 165

Мы видим, что за счёт уменьшения памяти очистки существенно участились, но это не сказалось на об-

щем результате. С помощью флага H[size] мы можем устанавливать рекомендуемое минимальное значение

для размера кучи. Оно точно не будет меньше. Вернёмся к первому варианту и выделим алгоритму побольше

памяти, например 20 Мб:

./sum +RTS -A1m -H20m —sstderr

5.00005e9

14,145,284 bytes allocated in the heap

319,716 bytes copied during GC

324,136 bytes maximum residency (1 sample(s))

60,888 bytes maximum slop

22 MB total memory in use (1 MB lost due to fragmentation)

Tot time (elapsed)

Avg pause

Max pause

Gen

0

2 colls,

0 par

0.00s

0.00s

0.0001s

0.0001s

Gen

1

1 colls,

0 par

0.00s

0.00s

0.0007s

0.0007s

INIT

time

0.00s

(

0.00s elapsed)

MUT

time

0.02s

(

0.02s elapsed)

GC

time

0.00s

(

0.00s elapsed)

EXIT

time

0.00s

(

0.00s elapsed)

Total

time

0.02s

(

0.02s elapsed)

%GC

time

0.0%

(4.4% elapsed)

Alloc rate

884,024,998 bytes per MUT second

Productivity 100.0% of total user, 78.6% of total elapsed

Произошла лишь одна глубокая очистка (похоже, что эта очистка соответствует начальному выделению

памяти) и продуктивность программы стала стопроцентной. С помощью флага S вместо s мы можем по-

смотреть более детальную картину управления памяти. Будут распечатаны показатели памяти для каждой

очистки.

./sum +RTS -Sfile

В файле file мы найдём такую таблицу:

память

время

выделено скопировано в живых

GC

Total

Тип очистки

Alloc

Copied

Live

GC

GC

TOT

TOT

Page Flts

bytes

bytes

bytes

user

elap

user

elap

545028

150088

174632

0.00

0.00

0.00

0.00

0

0

(Gen:

1)

523264

298956

324136

0.00

0.00

0.00

0.00

0

0

(Gen:

0)

Итак у нас появился один существенный показатель качества программ. Это количество глубоких очи-

сток. Во время глубокой очистки вычислитель производит две затратные операции: сканирование всей кучи

Читай продолжение на следующей странице
Добавить комментарии