и запрос у системы возможно большого блока памяти. Чем меньше таких очисток, тем лучше. Сократить их
число можно удачной комбинацией показателей A и H. Но не стоит сразу начинать обновлять параметры по
умолчанию, если ваша программа работает слишком медленно. Лучше сначала попробовать изменить ал-
горитм. Найти функцию, которая слишком много ленится и ограничить её с помощью seq или энергичных
образцов. В этом примере у нас была всего одна функция, поэтому поиск не составил труда. Но что если их
уже очень много? Скорее всего так и будет. Не стоит оптимизировать не рабочую программу. А в рабочей
программе обычно много функций. Но это не так страшно, помимо суммарных показателей GHC позволяет
собирать более конкретную статистику.
Стоит отметить функцию performGC из модуля System.Mem, она форсирует поверхностную сборку мусора.
Допустим вы чистаете какие-то данные из файла и тут же преобразуете их в структуру данных. После того
как чтение данных закончится, вы знаете, что промежуточные данные связаные с чтением вам уже не нужны.
Выполнив performGC вы можете подсказать об этом вычислителю.
Профилирование функций
Время и общий объём памяти
Процесс отслеживания показателей память/скорость называется профилированием программы. Всё вро-
де бы работает, но работает слишком медленно, необходимо установить причину. Рассмотрим такую про-
грамму:
166 | Глава 10: Реализация Haskell в GHC
module Main where
concatR = foldr (++) []
concatL = foldl (++) []
fun :: Double
fun = test concatL - test concatR
where test f = last $ f $ map return [1 .. 1e6]
main = print fun
У нас есть подозрение, что какая-то из двух функций concatX работает слишком медленно. Мы можем
посмотреть какая, если добавим к ним специальную прагму SCC:
concatR = {-# SCC ”right” #-} foldr (++) []
concatL = {-# SCC ”left”
#-} foldl (++) []
Напомню, что прагмой называется специальный блочный комментарий с решёткой. Это специальное со-
общение компилятору. Прагмой SCC мы устанавливаем так называемый центр затрат (cost center). Она пи-
шется сразу за знаком равно. В кавычках пишется имя, под которым статиситика войдёт в итоговый отчёт.
После этого вычислитель будет следить за нагрузкой, которая приходится на эту функцию. Теперь нам нужно
скомпилировать модуль с флагом prof, который активирует подсчёт статистики в центрах затрат:
$ ghc --make concat.hs -rtsopts -prof -fforce-recomp
$ ./concat +RTS -p
Второй командой мы запускаем программу и передаём вычислителю флаг p. После этого будет создан
файл concat. prof. Откроем этот файл:
concat +RTS -p -RTS
total time
=
1.45 secs
(1454 ticks @ 1000 us, 1 processor)
total alloc = 1,403,506,324 bytes
(excludes profiling overheads)
COST CENTRE MODULE
%time %alloc
left
Main
99.8
99.8
individual
inherited
COST CENTRE MODULE
no.
entries
%time %alloc
%time %alloc
MAIN
MAIN
46
0
0.0
0.0
100.0
100.0
CAF
GHC.Integer.Logarithms.Internals
91
0
0.0
0.0
0.0
0.0
CAF
GHC.IO.Encoding.Iconv
71
0
0.0
0.0
0.0
0.0
CAF
GHC.IO.Encoding
70
0
0.0
0.0
0.0
0.0
CAF
GHC.IO.Handle.FD
57
0
0.0
0.0
0.0
0.0
CAF
GHC.Conc.Signal
56
0
0.0
0.0
0.0
0.0
CAF
Main
53
0
0.2
0.2
100.0
100.0
right
Main
93
1
0.0
0.0
0.0
0.0
left
Main
92
1
99.8
99.8
99.8
99.8
Мы видим, что почти всё время работы программа провела в функции concatL. Функция concatR была
вычислена мгновенно (time) и почти не потребовала ресусов памяти (alloc). У нас есть две пары колонок ре-
зультатов. individual указывает на время вычисления функции, а inherited – на время вычисления функции
и всех дочерних функций. Колонка entries указывает число вызовов функции. Если мы хотим проверить все
функции мы можем не указывать функции прагмами. Для этого при компиляции указывается флаг auto-all.
Отметим также, что все константы определённый на самом верхнем уровне модуля, сливаются в один центр.
Они называются в отчёте как CAF. Для того чтобы вычислитель следил за каждой константой по отдельности