Время и общий объём памяти
Процесс отслеживания показателей память/скорость называется профилированием программы. Всё вро-
де бы работает, но работает слишком медленно, необходимо установить причину. Рассмотрим такую про-
грамму:
166 | Глава 10: Реализация Haskell в GHC
module Main where
concatR =
foldr (++) []concatL =
foldl (++) []fun :: Double
fun =
test concatL - test concatRwhere
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 -RTStotal time
=
1.45 secs
(1454 ticks @
1000 us, 1 processor)total alloc =
1,403,506,324 bytes(excludes profiling overheads)
COST CENTRE MODULE
%
time %allocleft
Main
99.8
99.8
individual
inherited
COST CENTRE MODULE
no.
entries
%
time %alloc%
time %allocMAIN
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 concatRfun2 =
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 getArgswhere
xs n = [1 .. 10 ^ n]sum2 ::
[Int] -> (Int, Int)sum2 =
iter (0, 0)where
iter c[]
=
citer c
(x:
xs) = iter (tick x c) xstick :: Int ->
(Int, Int) -> (Int, Int)tick x (c0, c1) |
even x=
(c0, c1 + 1)|
otherwise = (c0 + 1, c1)