haskell-notes

= 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 узнали бы в каких функциях

программа тратит больше всего ресурсов. После этого мы бы пошли смотреть исходный код подозрительных

функций и после внесённых изменений снова посмотрели бы на графики кучи.

Если подумать, что мы делаем? Мы создаём отложенное вычисление, которое обещает построить большой

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

элементу пары, а если не чётным, то к другому. Проблема в том, что внутри пары происходит накопление

отложенных вычислений, необходимо сразу вычислять значения перед запаковыванием их в пару. Изменим

код:

{-# 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).

Поиск источников внезапной остановки

case-выражения и декомпозиция в аргументах функции могут стать источником очень неприятных оши-

Страницы: 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