haskell-notes

программе обычно много функций. Но это не так страшно, помимо суммарных показателей 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 указывает число вызовов функции. Если мы хотим проверить все

функции мы можем не указывать функции прагмами. Для этого при компиляции указывается флаг autoall.

Отметим также, что все константы определённый на самом верхнем уровне модуля, сливаются в один центр.

Они называются в отчёте как CAF. Для того чтобы вычислитель следил за каждой константой по отдельности

необходимо указать флаг cafall. Попробуем на таком модуле:

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

[]

Страницы: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162