Скомпилируем с флагом профилирования:
$ 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 узнали бы в каких функциях
программа тратит больше всего ресурсов. После этого мы бы пошли смотреть исходный код подозрительных
функций и после внесённых изменений снова посмотрели бы на графики кучи.
Если подумать, что мы делаем? Мы создаём отложенное вычисление, которое обещает построить большой
список, вытягиваем из списка по одному элементу и, если элемент оказывается чётным, прибавляем к одному
элементу пары, а если не чётным, то к другому. Проблема в том, что внутри пары происходит накопление
отложенных вычислений, необходимо сразу вычислять значения перед запаковыванием их в пару. Изменим
код:
{-# Language BangPatterns #-}
module Main where
import System.Environment(getArgs)
Статистика выполнения программы | 169
leak 6 +RTS -K30m -hc -L45
2,489,935 bytes x seconds
Fri Jun 1 23:11 2012
bytes
14M
12M
(103)tick/sum2.iter/sum2/main/Main.CAF
10M
8M
(102)main.xs/main/Main.CAF
6M
4M
(101)sum2.iter/sum2/main/Main.CAF
2M
0M
0.0
0.0
0.0
0.1
0.1
0.1
0.1
0.1
0.2
0.2
0.2
0.2
seconds
Рис. 10.11: Профиль кучи для утечки памяти
leak 6 +RTS -K30m -hd -L45
3,016,901 bytes x seconds
Fri Jun 1 23:14 2012
bytes
14M
BLACKHOLE
12M
10M
I#
8M
6M
4M
2M
0M
0.0
0.1
0.1
0.2
0.2
0.2
seconds
Рис. 10.12: Профиль кучи для утечки памяти
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)
Мы сделали функцию tick строгой. Теперь посмотрим на профиль:
$ ghc --make leak2.hs -rtsopts -prof -auto-all
$ ./leak2 6 +RTS -K30m -hc
(500000,500000)
170 | Глава 10: Реализация Haskell в GHC
$ hp2ps -e80mm -c leak2.hp
Не получилось (рис. 10.13). Как же так. Посмотрим на расход памяти отдельных функций. tick стала
строгой, но этого не достаточно, потому что в первом аргументе iter накапливаются вызовы tick. Сделаем
iter строгой по первому аргументу:
leak2 6 +RTS -K30m -hc
1,854,625 bytes x seconds
Fri Jun 1 21:38 2012
bytes
12M
10M
(102)main.xs/main/Main.CAF
8M
6M
(101)sum2.iter/sum2/main/M...
4M
2M
0M
0.0
0.0
0.0
0.1
0.1
0.1
0.1
0.1
0.2
0.2
0.2
seconds
Рис. 10.13: Опять двойка
sum2 :: [Int] -> (Int, Int)
sum2 = iter (0, 0)
where iter ! c
[]
= c
iter ! c
(x:xs) = iter (tick x c) xs
Теперь снова посмотрим на профиль:
$ ghc --make leak2.hs -rtsopts -prof -auto-all
$ ./leak2 6 +RTS -K30m -hc
(500000,500000)
$ hp2ps -e80mm -c leak2.hp
Мы видим (рис. 10.14), что память резко подскакивает и остаётся постоянной. Но теперь показатели
измеряются не в мегабайтах, а в килобайтах. Мы справились. Остальные флаги hX позволяют наблюдать за
разными специфическими объектами в куче. Мы можем узнать сколько памяти приходится на разные модули
(hm), сколько памяти приходится на разные конструкторы (hd), на разные типы замыканий (hy).
Поиск источников внезапной остановки