Выбрать главу

необходимо указать флаг 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

[]

= c

iter c

(x:xs) = iter (tick x c) xs

tick :: Int -> (Int, Int) -> (Int, Int)

tick x (c0, c1) | even x

= (c0, c1 + 1)

| otherwise = (c0 + 1, c1)

Скомпилируем с флагом профилирования:

$ ghc --make leak.hs -rtsopts -prof -auto-all

Статистика вычислителя показывает, что эта программа вызывала глубокую очистку 8 раз и выполняла

полезную работу лишь 40% времени.

$ ./leak 6 +RTS -K30m -sstderr

...

Tot time (elapsed)

Avg pause

Max pause

Gen

0

493 colls,

0 par

0.26s

0.26s

0.0005s

0.0389s

Gen

1

8 colls,

0 par

0.14s

0.20s

0.0248s

0.0836s

...

Productivity

40.5% of total user, 35.6% of total elapsed

Теперь посмотрим на профиль кучи.

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

$ ./leak 6 +RTS -K30m -hc

(500000,500000)

$ hp2ps -e80mm -c leak.hp

В первой команде мы добавили флаг hc для того, чтобы создать файл с расширением . hp. Он содержит

таблицу с показателями размера кучи, которые вычислитель замеряет через равные промежутки времени. Мы

можем изменять интервал с помощью флага iN, где N – время в секундах. Второй командой мы преобразуем

профиль в картинку. Флаг c, говорит о том, что мы хотим получить цветную картинку, а флаг e80mm, говорит

о том, что мы собираемся вставить картинку в текст LaTeX. После e указан размер в миллиметрах. Мы видим

характерный горб (рис. 10.10).

leak 6 +RTS -K30m -hc

3,008,476 bytes x seconds

Fri Jun 1 21:17 2012

bytes

14M

12M

(103)tick/sum2.iter/sum2/m...

10M

8M

(102)main.xs/main/Main.CAF

6M

4M

(101)sum2.iter/sum2/main/M...

2M

0M

0.0

0.1

0.1

0.2

0.2

0.2

seconds

Рис. 10.10: Профиль кучи для утечки памяти

В картинку не поместились имена функций мы можем увеличить строку флагом L. Теперь все имена

поместились (рис. 10.11).

$ ./leak 6 +RTS -K30m -hc -L45

(500000,500000)

$ hp2ps -e80mm -c leak.hp

С помощью флага hd посмотрим на объекты, которые застряли в куче (рис. 10.12):

$ ./leak 6 +RTS -K30m -hd -L45

(500000,500000)

$ hp2ps -e80mm -c leak.hp

Теперь куча разбита по типу объектов (замыканий) (рис. 10.12). BLACKHOLE это специальный объект, ко-

торый заменяет THUNK во время его вычисления. I# – это скрытый конструктор Int. sat_sUa и sat_sUd – это

имена застрявших отложенных вычислений. Если бы наша программа была очень большой на этом месте мы

бы запустили профилирование по функциям с флагом p и из файла leak. prof узнали бы в каких функциях