Читаем Учебник по Haskell полностью

Время и общий объём памяти

Процесс отслеживания показателей память/скорость называется профилированием программы. Всё вро-

де бы работает, но работает слишком медленно, необходимо установить причину. Рассмотрим такую про-

грамму:

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

[]

= 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)

Перейти на страницу:

Похожие книги

C++: базовый курс
C++: базовый курс

В этой книге описаны все основные средства языка С++ - от элементарных понятий до супервозможностей. После рассмотрения основ программирования на C++ (переменных, операторов, инструкций управления, функций, классов и объектов) читатель освоит такие более сложные средства языка, как механизм обработки исключительных ситуаций (исключений), шаблоны, пространства имен, динамическая идентификация типов, стандартная библиотека шаблонов (STL), а также познакомится с расширенным набором ключевых слов, используемым в .NET-программировании. Автор справочника - общепризнанный авторитет в области программирования на языках C и C++, Java и C# - включил в текст своей книги и советы программистам, которые позволят повысить эффективность их работы. Книга рассчитана на широкий круг читателей, желающих изучить язык программирования С++.

Герберт Шилдт

Программирование, программы, базы данных