программе обычно много функций. Но это не так страшно, помимо суммарных показателей 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. Для того чтобы вычислитель следил за каждой константой по отдельности
необходимо указать флаг caf—all. Попробуем на таком модуле:
module Main where
fun1 = test concatL — test concatR
fun2 = test concatL + test concatR
Статистика выполнения программы | 167
test f = last $ f $ map return [1 .. 1e4]
concatR = foldr (++) []
concatL = foldl (++) []
main = print fun1 >> print fun2
Скомпилируем:
$ ghc —make concat2.hs -rtsopts -prof -auto-all -caf-all -fforce-recomp
$ ./concat2 +RTS -p
0.0
20000.0
После этого можно открыть файл concat2. prof и посмотреть итоговую статистику по всем значениям.
Программа с включённым профилированием будет работать гораздо медленей, не исключено, что ей не
хватит памяти на стеке, в этом случае вы можете добавить памяти с помощью флага вычислителя K, впрочем
если это произойдёт GHC подскажет вам что делать.
Динамика изменения объёма кучи
В предыдущем разделе мы смотрели общее время и память затраченные на вычисление функции. В этом
мы научимся измерять динамику изменения расхода памяти на куче. По этому показателю можно понять
в какой момент в программе возникают утечки памяти. Мы увидим характерные горбы на картинках, ко-
гда GC будет активно запрашивать новую память. Для этого сначала нужно скомпилировать программу с
флагом prof как и в предыдущем разделе, а при выполнении программы добавить один из флагов hc, hm,
hd, hy или hr. Все они начинаются с буквы h, от слова heap (куча). Вторая буква указывает тип графика,
какими показателями мы интересуемся. Все они создают специальный файл имяПриложения. hp, который мы
можем преобразовать в график в формате PostScript с помощью программы hp2ps, она устанавливается
автоматически вместе с GHC.
Рассмотрим типичную утечку памяти (из упражнения к предыдущей главе):
module Main where
import System.Environment(getArgs)
main = print . sum2 . xs . read =<< fmap head getArgs
where xs n = [1 .. 10 ^ n]
sum2 :: [Int] -> (Int, Int)
sum2 = iter (0, 0)
where iter c
[]