Spec-Zone.ru › Haskell 8

8. Профилирование

GHC поставляется со средствами профилирования времени и памяти, позволяющими отвечать на вопросы вроде «Почему моя программа работает так медленно?» или «Почему моя программа потребляет так много памяти?».

Профилирование программы — трехэтапный процесс:

  1. Перекомпилируйте свою программу для профилирования с помощью опции -prof и, вероятно, одной из опций для добавления автоматических аннотаций: -fprof-auto является наиболее распространённой 1.

    Если вы используете внешние пакеты с cabal, вам может потребоваться переустановить эти пакеты с поддержкой профилирования; обычно это делается с помощью cabal install -p package --reinstall.

  2. После компиляции программы для профилирования, необходимо запустить её для генерации профиля. Например, простой профиль времени можно получить, запустив программу с +RTS -p (см. -p), что сгенерирует файл с именем prog.prof, где ⟨prog⟩ — имя вашей программы (без расширения .exe , если вы используете Windows).

    Существует множество различных типов профилей, которые можно сгенерировать, используя различные параметры RTS. В последующих разделах этой главы мы опишем различные типы профилей. Некоторые профили требуют дополнительной обработки с помощью дополнительных инструментов после запуска программы.

  3. Ознакомьтесь с сгенерированной информацией о профилировании, используйте её для оптимизации программы и повторяйте при необходимости.

8.1. Центры затрат и стеки центров затрат

Система профилирования GHC распределяет затраты по центрам затрат. Затраты — это просто время или объем памяти (оперативная память), необходимый для оценки выражения. Центры затрат — это аннотации программы вокруг выражений; все затраты, понесенные аннотированным выражением, присваиваются окружающему центру затрат. Кроме того, GHC будет запоминать стек окружающих центров затрат для любого данного выражения во время выполнения и генерировать дерево вызовов распределения затрат.

Давайте рассмотрим пример:

main = print (fib 30)
fib n = if n < 2 then 1 else fib (n-1) + fib (n-2)

Скомпилируйте и запустите эту программу следующим образом:

$ ghc -prof -fprof-auto -rtsopts Main.hs
$ ./Main +RTS -p
121393
$

При запуске скомпилированной GHC программой с параметром RTS -p, она генерирует файл под названием prog.prof. В данном случае файл будет содержать что-то вроде этого:

        Wed Oct 12 16:14 2011 Time and Allocation Profiling Report  (Final)

           Main +RTS -p -RTS

        total time  =        0.68 secs   (34 ticks @ 20 ms)
        total alloc = 204,677,844 bytes  (excludes profiling overheads)

COST CENTRE MODULE  %time %alloc

fib         Main    100.0  100.0


                                                      individual     inherited
COST CENTRE MODULE                  no.     entries  %time %alloc   %time %alloc

MAIN        MAIN                    102           0    0.0    0.0   100.0  100.0
 CAF        GHC.IO.Handle.FD        128           0    0.0    0.0     0.0    0.0
 CAF        GHC.IO.Encoding.Iconv   120           0    0.0    0.0     0.0    0.0
 CAF        GHC.Conc.Signal         110           0    0.0    0.0     0.0    0.0
 CAF        Main                    108           0    0.0    0.0   100.0  100.0
  main      Main                    204           1    0.0    0.0   100.0  100.0
   fib      Main                    205     2692537  100.0  100.0   100.0  100.0

Первая часть файла содержит имя программы и параметры, а также общее время и общее выделение памяти, измеренные во время выполнения программы (обратите внимание, что общее выделение памяти не совпадает с объемом активной памяти, необходимой программе в любой момент времени; последнее можно определить с помощью профилирования кучи, которое мы опишем позже в Профилирование использования памяти).

Вторая часть файла — это детализация по центрам затрат наиболее затратных функций в программе. В этом случае в программе была только одна значимая функция, а именно fib, и она была ответственна за 100% затрат времени и затрат на выделение памяти в программе.

Третий и последний раздел файла предоставляет детализацию профиля по стеку центра затрат. Это приблизительный профиль дерева вызовов программы. В приведенном примере ясно, что дорогостоящий вызов fib произошел из main.

Затраты времени и выделения памяти для данной части программы отображаются двумя способами: «индивидуальные», которые представляют собой затраты, понесенные кодом, охватываемым этим стеком центра затрат, и «унаследованные», которые включают затраты всех потомков этого узла.

Полезность стеков центров затрат лучше демонстрируется путем небольшого изменения примера:

main = print (f 30 + g 30)
  where
    f n  = fib n
    g n  = fib (n `div` 2)

fib n = if n < 2 then 1 else fib (n-1) + fib (n-2)

Скомпилируйте и запустите эту программу как раньше и посмотрите на новые результаты профилирования:

COST CENTRE MODULE                  no.     entries  %time %alloc   %time %alloc

MAIN        MAIN                    102           0    0.0    0.0   100.0  100.0
 CAF        GHC.IO.Handle.FD        128           0    0.0    0.0     0.0    0.0
 CAF        GHC.IO.Encoding.Iconv   120           0    0.0    0.0     0.0    0.0
 CAF        GHC.Conc.Signal         110           0    0.0    0.0     0.0    0.0
 CAF        Main                    108           0    0.0    0.0   100.0  100.0
  main      Main                    204           1    0.0    0.0   100.0  100.0
   main.g   Main                    207           1    0.0    0.0     0.0    0.1
    fib     Main                    208        1973    0.0    0.1     0.0    0.1
   main.f   Main                    205           1    0.0    0.0   100.0   99.9
    fib     Main                    206     2692537  100.0   99.9   100.0   99.9

Теперь, хотя в программе были два вызова fib , сразу становится ясно, что вызов из f занял всё время. Функции f и g , определённые в разделе where в main , получают свои собственные центры затрат, main.f и main.g соответственно.

Фактическое значение различных столбцов в выводе:

Число вхождений данной точки в дереве вызовов.

Процент общего времени выполнения программы, потраченного в данной точке дерева вызовов.

Процент общего выделения памяти (без учета накладных расходов на профилирование) программы, выполненный этим вызовом.

Процент общего времени выполнения программы, потраченного ниже данной точки в дереве вызовов.

Процент общего выделения памяти (без учета накладных расходов на профилирование) программы, выполненный этим вызовом и всеми его дочерними вызовами.

Кроме того, вы можете использовать параметр RTS -P, чтобы получить следующую дополнительную информацию:

ticks

Числовой показатель «тиков» времени, присвоенный данному центру затрат; из него получается значение %time , упомянутое выше.

bytes

Количество байтов, выделенных в куче во время работы в этом центре затрат; снова же, это исходное число, из которого получается значение %alloc , упомянутое выше.

Что насчёт рекурсивных функций и взаимно рекурсивных групп функций? Где присваиваются затраты? Ну, хотя GHC и сохраняет информацию о том, какие группы функций рекурсивно вызывают друг друга, эта информация не отображается в базовом профиле времени и выделения памяти, вместо этого граф вызовов сглаживается в дерево следующим образом: вызов функции, который встречается в другом месте на текущем стеке, не добавляет другую запись в стек; вместо этого затраты на этот вызов агрегируются в вызывающий его элемент 2.

8.1.1. Вставка центров затрат вручную

Центры затрат — это всего лишь аннотации программы. Когда вы указываете -fprof-auto компилятору, он автоматически вставляет аннотацию центра затрат вокруг каждого связывания, не помеченного INLINE в вашей программе, но вы можете свободно добавлять аннотации центров затрат самостоятельно.

Синтаксис аннотации центра затрат для выражений:

{-# SCC "name" #-} <expression>

где "name" — произвольная строка, которая станет именем вашего центра затрат в выводе профилирования, а <expression> — любое выражение Haskell. Аннотация SCC простирается как можно дальше вправо при разборе. (SCC означает «Установить центр затрат»). Двойные кавычки можно опустить, если name — идентификатор Haskell, например:

{-# SCC id #-} <expression>

Аннотации центров затрат также могут появляться на верхнем уровне или в контексте объявления. В этом случае вам нужно передать имя функции, определённое в том же модуле или области видимости с аннотацией. Пример:

f x y = ...
  where
    g z = ...
    {-# SCC g #-}

{-# SCC f #-}

Если вы хотите дать центру затрат другое имя, отличное от имени функции, вы можете передать строку в аннотацию

f x y = ...
{-# SCC f "cost_centre_name" #-}

Вот пример программы с парой SCC:

main :: IO ()
main = do let xs = [1..1000000]
          let ys = [1..2000000]
          print $ {-# SCC last_xs #-} last xs
          print $ {-# SCC last_init_xs #-} last $ init xs
          print $ {-# SCC last_ys #-} last ys
          print $ {-# SCC last_init_ys #-} last $ init ys

что даёт такой профиль при запуске:

COST CENTRE     MODULE                  no.     entries  %time %alloc   %time %alloc

MAIN            MAIN                    102           0    0.0    0.0   100.0  100.0
 CAF            GHC.IO.Handle.FD        130           0    0.0    0.0     0.0    0.0
 CAF            GHC.IO.Encoding.Iconv   122           0    0.0    0.0     0.0    0.0
 CAF            GHC.Conc.Signal         111           0    0.0    0.0     0.0    0.0
 CAF            Main                    108           0    0.0    0.0   100.0  100.0
  main          Main                    204           1    0.0    0.0   100.0  100.0
   last_init_ys Main                    210           1   25.0   27.4    25.0   27.4
   main.ys      Main                    209           1   25.0   39.2    25.0   39.2
   last_ys      Main                    208           1   12.5    0.0    12.5    0.0
   last_init_xs Main                    207           1   12.5   13.7    12.5   13.7
   main.xs      Main                    206           1   18.8   19.6    18.8   19.6
   last_xs      Main                    205           1    6.2    0.0     6.2    0.0

8.1.2. Правила распределения затрат

Во время выполнения программы с включенным профилированием, GHC поддерживает скрытый стек центра затрат и присваивает любые затраты (выделение памяти и время) тому, что является текущим стеком центра затрат в момент возникновения затрат.

Механизм прост: всякий раз, когда программа вычисляет выражение с аннотацией SCC, {-# SCC c -#} E, центр затрат c помещается на текущий стек, а счётчик входов для этого стека увеличивается на единицу. Иногда стек также нужно сохранять и восстанавливать; в частности, когда программа создаёт ленивую приостановку (thunk), текущий стек центра затрат сохраняется в thunk и восстанавливается при его вычислении. Таким образом, стек центра затрат независим от фактического порядка вычислений, используемого GHC во время выполнения.

При вызове функции GHC берёт стек, сохранённый в вызываемой функции (который для функции верхнего уровня будет пустым), и присоединяет его к текущему стеку, игнорируя любой префикс, совпадающий с префиксом текущего стека.

Ранее мы упоминали, что ленивые вычисления, то есть thunks, захватывают текущий стек при их создании и восстанавливают его при вычислении. Что насчёт thunks верхнего уровня? Они «создаются» при компиляции программы, поэтому какой стек мы должны им присвоить? Техническое название thunks верхнего уровня — CAF («Постоянная применительная форма»). GHC присваивает каждому CAF в модуле стек, состоящий из одного центра затрат M.CAF, где M — имя модуля. Также возможно присвоить каждому CAF разный стек, используя опцию -fprof-cafs. Это особенно полезно при компиляции с помощью -ffull-laziness (по умолчанию с -O и выше), так как константы в телах функций будут подняты на верхний уровень и станут CAFs. Скорее всего, вам потребуется обратиться к ядру (-ddump-simpl), чтобы определить, чему соответствуют эти CAFs.

8.2. Параметры компилятора для профилирования

-prof

Для использования системы профилирования все модули должны быть скомпилированы и связаны с помощью опции -prof. Любые аннотации SCC , которые вы поместили в свой исходный код, оживут.

Без опции -prof ваши аннотации SCC игнорируются; поэтому вы можете компилировать код, содержащий SCC , не меняя его.

Есть несколько других параметров компиляции, относящихся к профилированию. Используйте их в дополнение к -prof. Для всех модулей программы их использовать не обязательно.

-fprof-auto

Все связывания, не помеченные INLINE, независимо от того, экспортируются они или нет, на верхнем или вложенном уровне, получат автоматические аннотации SCC. Функции, помеченные INLINE, должны получить центр затрат вручную.

END_OF_DOCUMENT_MARKER
-fprof-auto-top

GHC автоматически добавит аннотации SCC для всех связей верхнего уровня, не помеченных INLINE. Если вам нужен центр затрат для функции INLINE, вы должны добавить его вручную.

-fprof-auto-exported

GHC автоматически добавит аннотации SCC для всех экспортированных функций, не помеченных INLINE. Если вам нужен центр затрат для функции INLINE, вы должны добавить его вручную.

-fprof-auto-calls

Добавляет автоматическую аннотацию SCC ко всем сайтам вызовов. Это особенно полезно при использовании профилирования для создания стеков вызовов; см. функцию Debug.Trace.traceShow или флаг RTS -xc (Опции RTS для хакеров, отладчиков и слишком заинтересованных) для получения более подробной информации.

-fprof-cafs

Затраты всех CAFs в модуле обычно приписываются одному «большому» центру затрат CAF. С этой опцией все CAFs получают собственный центр затрат. Вариант «если все остальное не сработает»…

-fno-prof-auto

Отключает предыдущие опции -fprof-auto, -fprof-auto-top или -fprof-auto-exported.

-fno-prof-cafs

Отключает предыдущую опцию -fprof-cafs.

-fno-prof-count-entries

Указывает GHC не собирать информацию о частоте входа в функции во время выполнения (столбец «entries» в профиле времени) для данного модуля. Это, как правило, ускоряет профилируемый код и приближает его к скорости не профилируемого кода, так как GHC может более агрессивно оптимизировать, если ему не нужно поддерживать корректные счетчики входа. Эта опция может быть полезной, если вам не нужны счетчики входа (например, если вы планируете только профилирование кучи).

8.3. Профилирование времени и выделения памяти

Для создания профиля времени и выделения памяти укажите одну из следующих опций RTS для скомпилированной программы при ее запуске (опции RTS должны быть заключены в +RTS ... -RTS как обычно):

-p
-P
-pa

Опция -p создает стандартный отчет о профиле времени. Он записывается в файл <stem>.prof; по умолчанию основой имени является имя программы, но может быть переопределено флагом -po ⟨stem⟩.

Опция -P создает более подробный отчет, содержащий фактические данные о времени и выделении памяти. (Практически не используется.)

Опция -pa создает наиболее подробный отчет, содержащий все центры затрат в дополнение к фактическим данным о времени и выделении памяти.

-pj

Опция -pj создает отчет о профиле времени/выделения памяти в формате JSON, записанный в файл <program>.prof.

-po ⟨stem⟩

Опция -po ⟨stem⟩ переопределяет основу, используемую для формирования путей выходных файлов для профилирования центра затрат (см. флаги -p и -pj выше) и профилирования кучи (см. -h).

Например, запуск программы с +RTS -h -p -pohello-world приведет к созданию профиля кучи с именем hello-world.hp и профиля центра затрат с именем hello-world.prof.

-V ⟨secs⟩
По умолчанию

0.02

Устанавливает интервал, с которым отсчитывает время RTS, который также является интервалом выборки профиля времени и выделения памяти. По умолчанию значение 0.02 секунды. Временная база данных использует единственный сигнал таймера для подсчета тиков; этот сигнал таймера используется для управления таймером переключения контекста (Использование конкурентного Haskell) и таймером профилирования кучи Опции RTS для профилирования кучи. Также профилировщик времени напрямую использует сигнал таймера RTS для записи образцов профилирования времени.

Обычно устанавливать опцию -V ⟨secs⟩ напрямую не нужно: разрешение таймера RTS автоматически корректируется, если короткий интервал запрошен с опциями -C ⟨s⟩ или -i ⟨secs⟩. Однако, установка -V ⟨secs⟩ требуется для повышения разрешения профилировщика времени.

Использование значения нуль полностью отключает таймер RTS и приводит к отключению таймеров, зависящих от него: таймера переключения контекста и таймера профилирования кучи. Переключение контекстов все равно будет происходить, но детерминированно и со скоростью намного большей, чем обычно. Отключение таймера интервала полезно для отладки, поскольку устраняет источник недетерминизма во время выполнения.

-xc

Эта опция заставляет среду выполнения печатать текущий стек центра затрат всякий раз, когда возникает исключение. Это может быть особенно полезно для отладки местоположения исключений, таких как печально известная ошибка Prelude.head: empty list. См. Опции RTS для хакеров, отладчиков и слишком заинтересованных.

8.3.1. Формат профиля JSON

При вызове с флагом -pj среда выполнения выведет профиль центра затрат в удобочитаемом машинном формате JSON. Объект верхнего уровня этого формата имеет следующие свойства,

program (string)

Имя программы

arguments (list of strings)

Аргументы командной строки, переданные программе

rts_arguments (list of strings)

Аргументы командной строки, переданные системе выполнения

initial_capabilities (integral number)

Количество возможностей, с которыми программа была запущена (например, с помощью опции -N ⟨x⟩). Обратите внимание, что количество возможностей может меняться во время выполнения из-за функции setNumCapabilities.

total_time (number)

Общее время выполнения программы в секундах.

total_ticks (integral number)

Количество «тиков» профилировщика, прошедших в ходе выполнения программы.

end_time (number)

Приблизительное время завершения выполнения программы в качестве метки времени эпохи Unix.

tick_interval (float)

Время между тиками профилировщика.

total_alloc (integer)

Совокупные выделения программы в байтах.

cost_centres (list of objects)

Список центров затрат программы

profile (object)

Сама структура профиля

Каждый элемент в cost_centres — это объект, описывающий центр затрат программы, имеющий следующие свойства,

id (integral number)

Уникальный идентификатор, используемый для ссылки на центр затрат

is_caf (boolean)

Является ли центр затрат Постоянной Применимой Формой (CAF)

label (string)

Дескриптивный строковый идентификатор центра затрат.

src_loc (string)

Строка, описывающая фрагмент исходного кода, содержащий центр затрат.

Сами данные профиля описываются полем profile , которое содержит древовидный объект (который мы здесь будем называть «стеком центра затрат») со следующими свойствами,

id (integral number)

id центра затрат, указанного в списке cost_centres.

entries (integral number)

Сколько раз этот центр затрат был вызван?

ticks (integral number)

Сколько тиков выполнения программы находилось внутри этого центра затрат? Это не включает дочерние центры затрат.

alloc (integral number)

Сколько байтов выделила программа во время нахождения внутри этого центра затрат? Это не включает выделения в дочерних центрах затрат.

children (list)

Список, содержащий стеки дочерних центров затрат.

Например, простой профиль может выглядеть так,

{
  "program": "Main",
  "arguments": [
    "nofib/shootout/n-body/Main",
    "50000"
  ],
  "rts_arguments": [
    "-pj",
    "-hy"
  ],
  "end_time": "Thu Feb 23 17:15 2017",
  "initial_capabilities": 0,
  "total_time": 1.7,
  "total_ticks": 1700,
  "tick_interval": 1000,
  "total_alloc": 3770785728,
  "cost_centres": [
    {
      "id": 168,
      "label": "IDLE",
      "module": "IDLE",
      "src_loc": "<built-in>",
      "is_caf": false
    },
    {
      "id": 156,
      "label": "CAF",
      "module": "GHC.Integer.Logarithms.Internals",
      "src_loc": "<entire-module>",
      "is_caf": true
    },
    {
      "id": 155,
      "label": "CAF",
      "module": "GHC.Integer.Logarithms",
      "src_loc": "<entire-module>",
      "is_caf": true
    },
    {
      "id": 154,
      "label": "CAF",
      "module": "GHC.Event.Array",
      "src_loc": "<entire-module>",
      "is_caf": true
    }
  ],
  "profile": {
    "id": 162,
    "entries": 0,
    "alloc": 688,
    "ticks": 0,
    "children": [
      {
        "id": 1,
        "entries": 0,
        "alloc": 208,
        "ticks": 0,
        "children": [
          {
            "id": 22,
            "entries": 1,
            "alloc": 80,
            "ticks": 0,
            "children": []
          }
        ]
      },
      {
        "id": 42,
        "entries": 1,
        "alloc": 1632,
        "ticks": 0,
        "children": []
      }
    ]
  }
}

8.4. Профилирование использования памяти

Помимо профилирования поведения программы по времени и выделению памяти, вы также можете создать график использования памяти во времени. Это полезно для выявления причин утечек памяти, когда ваша программа удерживает больше памяти во время выполнения, чем ей нужно. Утечки памяти приводят к замедлению выполнения из-за интенсивной работы сборщика мусора и могут даже привести к тому, что программа выйдет из памяти.

Для создания профиля кучи вашей программы:

  1. Скомпилируйте программу для профилирования (Параметры компилятора для профилирования).
  2. Запустите её с одним из вариантов профилирования кучи, описанных ниже (например, -h для базового профиля производителя). Это создаёт файл prog.hp.

    Если включён журнал событий (с флагом системной среды выполнения -l ⟨flags⟩) примеры кучи будут дополнительно выводиться в журнал событий GHC (см. Вывод журнала событий профилировщика кучи для подробностей о формате событий).

  3. Запустите hp2ps для создания файла PostScript, prog.ps. Утилита hp2ps подробно описана в hp2ps – Рендеринг профилей кучи в PostScript.
  4. Отобразите профиль кучи с помощью просмотра PostScript, например, Ghostview, или распечатайте его на принтере, поддерживающем PostScript.

Например, вот профиль кучи, созданный для программы sphere из набора бенчмарков GHC nofib,

_images/prof_scc.svg

Возможно, вам также захочется взглянуть на hp2any, более продвинутый набор инструментов (не поставляемый с GHC) для отображения профилей кучи.

8.4.1. Параметры RTS для профилирования кучи

Можно сгенерировать несколько типов профилей кучи. Все различные типы профилей дают график активной кучи во времени, но они отличаются тем, как активная куча разбивается на области. Следующие параметры RTS выбирают, какой способ разбивки использовать:

-hT

Разбивает график по типу закрытия кучи.

-hc
-h

Требуется -prof. Разбивает график по стеку центра затрат, который произвёл данные.

Примечание

Значение сокращённого -h зависит от того, была ли ваша программа скомпилирована для профилирования. При компиляции для профилирования -h эквивалентно -hc, но в противном случае эквивалентно -hT (см. Параметры RTS для профилирования).

-hm

Требуется -prof. Разбивает активную кучу по модулю, содержащему код, который произвёл данные.

-hd

Требуется -prof. Разбивает график по описанию замыкания. Для фактических данных описанием является просто имя конструктора, для других замыканий это строка, сгенерированная компилятором, идентифицирующая замыкание.

-hy

Требуется -prof. Разбивает график по типу. Для замыканий с функцией типа или неизвестным/полиморфным типом строка будет представлять приближение к фактическому типу.

-hr

Требуется -prof. Разбивает график по набору удерживателей. Подробное описание профилирования удерживателей приведено ниже (Профилирование удерживателей).

-hb

Требуется -prof. Разбивает график по истории. Более подробное описание биографического профилирования приведено ниже (Биографическое профилирование).

-l

Выводить примеры профиля в журнал событий GHC. Этот формат более выразителен, чем старый формат .hp, и его можно сопоставить с другими событиями в течение выполнения программы. См. Вывод журнала событий профилировщика кучи для подробностей о структуре сгенерированных событий.

Кроме того, профиль может быть ограничен данными кучи, удовлетворяющими определённым критериям — например, вы можете отобразить профиль по типу, но только для данных, созданных определённым модулем, или профиль по удерживателям для определённого типа данных. Ограничения задаются следующим образом:

-hc ⟨name⟩

Ограничить профиль замыканиями, произведёнными стеками центров затрат, в которых один из указанных центров затрат находится вверху.

-hC ⟨name⟩

Ограничить профиль замыканиями, произведёнными стеками центров затрат, в которых один из указанных центров затрат находится где-либо в стеке.

-hm ⟨module⟩

Ограничить профиль замыканиями, произведёнными указанными модулями.

-hd ⟨desc⟩

Ограничить профиль замыканиями с указанными строками описаний.

-hy ⟨type⟩

Ограничить профиль замыканиями с указанными типами.

-hr ⟨cc⟩

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

-hb ⟨bio⟩

Ограничить профиль замыканиями с одним из указанных биографий, где ⟨bio⟩ — это одно из lag, drag, void, или use.

Например, следующие параметры сгенерируют профиль удерживателей, ограниченный конструкторами Branch и Leaf:

prog +RTS -hr -hdBranch,Leaf

Может быть только один параметр «разбиения» (например, -hr в приведённом выше примере), но количество дополнительных ограничений не ограничено. Все параметры могут быть объединены, за исключением одного: GHC в настоящее время не поддерживает смешивание параметров -hr и -hb.

Есть ещё три параметра, относящиеся к профилированию кучи:

-i ⟨secs⟩

Установить интервал профилирования (выборки) на ⟨secs⟩ секунд (по умолчанию 0,1 секунды). Допускаются дроби: например, -i0.2 получит 5 выборок в секунду. Это влияет только на профилирование кучи; временные профили всегда выбираются с частотой системного таймера RTS. См. Профилирование времени и выделения для изменения этого.

-xt

Включить в профиль кучи память, занимаемую потоками. Каждый поток занимает небольшой участок для своего состояния потока помимо места, выделенного для его стека (стеки обычно начинаются с малого размера, а затем увеличиваются по мере необходимости).

Это включает основной поток, поэтому использование -xt — хороший способ увидеть, сколько места для стека использует программа.

Память, занимаемая потоками и их стеками, обозначается как «TSO» и «STACK» соответственно при отображении профиля по описанию замыкания или описанию типа.

-L ⟨num⟩

Устанавливает максимальную длину имени стека центра затрат в профиле кучи. По умолчанию 25.

8.4.2. Профилирование удерживателей

Профилирование удерживателей предназначено для ответа на вопросы типа «почему эти данные удерживаются?». Начнём с определения удерживателя:

Удерживателем является либо системный стек, либо невычисленное замыкание (ленивая переменная), либо явным образом изменяемый объект.

В частности, конструкторы не являются удерживателями.

Объект B удерживает объект A если (i) B является объектом удерживателя и (ii) объект A достижим путём рекурсивного следования указателей, начиная с объекта B, но не встречая при этом других объектов удерживателей по пути. Каждый живой объект удерживается одним или несколькими объектами-удерживателями, которые вместе образуют его набор удерживателей, или его набор удерживателей, или его удерживатели.

При запросе профилирования удерживателей путём указания параметра -hr генерируется график, разбитый по наборам удерживателей. Набор удерживателей отображается как набор стеков центров затрат; поскольку это обычно слишком большой объём для размещения на графике профиля, каждый набор удерживателей нумеруется и отображается на графике в сокращённом виде вместе со своим номером, а полный список наборов удерживателей выводится в файл prog.prof.

Профилирование удерживателей требует нескольких проходов по активной куче, чтобы определить полный набор удерживателей для каждого объекта, что может быть довольно медленным. Поэтому мы устанавливаем ограничение на максимальный размер набора удерживателей, где все наборы удерживателей, превышающие максимальный размер набора удерживателей, заменяются специальным набором MANY. Максимальный размер набора по умолчанию составляет 8 и может быть изменён с помощью параметра RTS -R ⟨size⟩:

-R ⟨size⟩

Ограничить количество элементов в наборе удерживателей до ⟨size⟩ (по умолчанию 8).

8.4.2.1. Рекомендации по использованию профилирования удерживателей

Определение удерживающих объектов предназначено для отражения распространённой причины утечек памяти: большая структура удерживается невычисленным вычислением и будет освобождена после принудительного вычисления. Хорошим примером является поиск значения в конечном отображении, где, если поиск не будет выполнен своевременно, невычисленное обращение к нему приведёт к удержанию всего отображения. Такие утечки памяти часто можно устранить, заставив соответствующие вычисления выполняться немедленно, используя seq или аннотации строгости для полей конструкторов данных.

Часто определённая структура данных удерживается цепочкой невычисленных замыканий, причём только ближайшее из них будет сообщено профилированием удерживающих объектов – например, A удерживает B, B удерживает C, а C удерживает большую структуру. Может быть много B , но только одно A, поэтому A – это то, что мы действительно хотим устранить. Однако профилирование удерживающих объектов в этом случае сообщит о B как о удерживающем объекте большой структуры. Чтобы продвинуться выше по цепочке удерживающих объектов, мы можем запросить ещё один профиль удерживающих объектов, но на этот раз ограничить профиль B объектами, так что мы получим профиль удерживающих объектов B:

prog +RTS -hr -hcB

Этот трюк не гарантирует успеха, так как на куче могут быть и другие B замыкания, которые не являются интересующими нас удерживающими объектами, но в большинстве случаев мы обнаружили, что это полезный метод.

8.4.3. Профилирование жизненного цикла

Типичный объект кучи может находиться в одном из следующих четырёх состояний в любой момент своего жизненного цикла:

  • Этап ожидания, который является временем между созданием и первым использованием объекта,
  • Этап использования, который длится от первого использования до последнего использования объекта, и
  • Этап ожидания, который длится от последнего использования до того момента, когда последняя ссылка на объект будет удалена.
  • Объект, который никогда не используется, считается находящимся в состоянии «пустоты» в течение всего своего жизненного цикла.

Профиль жизненного цикла кучи отображает часть активной кучи в каждом из четырёх перечисленных выше состояний. Обычно наиболее интересными состояниями являются состояния «пустоты» и «ожидания»: активная куча в этих состояниях с большей вероятностью является неиспользуемым пространством, чем куча в состояниях «ожидание» или «использование».

Также можно разбить кучу на одно или несколько из этих состояний по другим критериям, ограничив профиль жизненным циклом. Например, чтобы показать часть кучи в состоянии «ожидание» или «пустоты» по производителю:

prog +RTS -hc -hbdrag,void

После того, как вы узнаете производителя или тип кучи в состояниях «ожидание» или «пустоты», следующим шагом обычно является поиск удерживающего(их) объекта(ов):

prog +RTS -hr -hccc...

Примечание

Этот двухэтапный процесс необходим, потому что GHC в настоящее время не может производить профилирование с одновременным использованием информации о жизненном цикле и удерживающих объектах.

8.4.4. Фактическая резиденция памяти

Как резиденция кучи, отчёт о которой даёт профайлер кучи, связана с фактической резиденцией памяти вашей программы при её выполнении? Вы можете увидеть значительную разницу между резиденцией, отчёт о которой даёт профайлер кучи, и резиденцией, отчёт о которой дают инструменты вашей системы (например, ps или top в Unix или Диспетчер задач в Windows). Причин этому несколько:

  • Есть накладные расходы на само профилирование, которые вычитаются из значений резиденции профайлером. Конечно, эти накладные расходы исчезают при компиляции без поддержки профилирования. Накладные расходы на место сейчас составляют 2 дополнительных слова на каждый объект кучи, что, вероятно, приводит к примерно 30%-ному накладным расходам.
  • Для сбора мусора требуется больше памяти, чем фактическая резиденция. Коэффициент зависит от используемого алгоритма сбора мусора: основной сбор мусора в стандартном копирующем коллекторе поколений обычно требует \(3L\) байтов памяти, где \(L\) – количество активных данных. Это связано с тем, что по умолчанию (см. параметр RTS -F ⟨factor⟩) мы разрешаем старому поколению расти до удвоенного размера (\(2L\)) перед его сбором, и нам дополнительно требуется \(L\) байтов для копирования активных данных. При использовании компактного сбора (см. параметр -c) это сокращается до \(2L\), и его можно ещё уменьшить, настроив параметр -F ⟨factor⟩. Также необходимо добавить размер области выделения (см. -A ⟨size⟩).
  • По умолчанию в профиль кучи не включается стек. См. параметр RTS -xt.
  • Сам текст программы, Стек, любые данные, не хранящиеся в куче (например, данные, выделенные внешними библиотеками, и данные, выделенные RTS), а также mmap() памяти не учитываются в профиле кучи.

8.5. hp2ps – Вывод профилей кучи в PostScript

Использование:

hp2ps [flags] [<file>[.hp]]

Программа hp2ps преобразует файл .hp , созданный с помощью параметра -h<break-down> среды выполнения, в график профиля кучи в PostScript. По соглашению, файл, который обрабатывает hp2ps, имеет расширение .hp. Выходной PostScript-файл записывается в file@.ps. Если <file> опущен, то программа работает как фильтр.

hp2ps распространяется в ghc/utils/hp2ps в дистрибутиве исходного кода GHC. Он был первоначально разработан Дейвом Уэкелингом как часть профайлера кучи HBC/LML.

Параметры:

-d

Для повышения удобочитаемости графиков hp2ps сортирует заштрихованные полосы для каждого идентификатора. По умолчанию полосы с наибольшей площадью располагаются поверх более мелких. Параметр -d приводит к тому, что более грубые полосы (те, которые представляют серии значений с наибольшими стандартными отклонениями) будут располагаться поверх более гладких.

-b

Обычно hp2ps помещает заголовок графика в небольшую рамку в верхней части страницы. Однако, если строка JOB слишком длинная, чтобы поместиться в небольшую рамку (более 35 символов), то hp2ps выберет использование большой рамки вместо этого. Параметр -b заставляет hp2ps использовать большую рамку.

-e⟨float⟩[in|mm|pt]

Генерировать инкапсулированный PostScript, пригодный для включения в документы LaTeX. Обычно график PostScript отображается в альбомной ориентации на области шириной 9 дюймов и высотой 6 дюймов, и hp2ps обеспечивает, чтобы эта область приблизительно центрировалась на листе бумаги формата А4. Этот формат удобен для детального изучения графика, но он непригоден для включения в документы LaTeX. Параметр -e заставляет график отображаться в книжной ориентации, при этом float указывает ширину в дюймах, миллиметрах или пунктах (по умолчанию). Полученный PostScript-файл соответствует соглашению Encapsulated PostScript (EPS), и его можно включить в документ LaTeX с помощью преобразователя dvi в PostScript Рокицкого dvips.

-g

Создать вывод, пригодный для просмотра PostScript gs (или аналогичного). В этом случае график отображается в книжной ориентации без масштабирования. Вывод непригоден для лазерного принтера.

-l

Обычно профиль ограничен 20 полосами, а дополнительные идентификаторы группируются в полосу OTHER. Флаг -l отменяет это ограничение на 20 полос, создавая необходимое количество полос. Ключ не создаётся, поскольку он не поместится!. Это полезно для профилей создания времени с большим количеством полос.

-m⟨int⟩

Обычно профиль ограничен 20 полосами, а дополнительные идентификаторы группируются в полосу OTHER. Флаг -m указывает на альтернативное ограничение числа полос (максимум 20).

-m0 запрашивает удаление ограничения на число полос. Создаётся необходимое количество полос. Однако ключ не создаётся, поскольку он не поместится! Это полезно для отображения профилей времени создания с большим количеством полос.

-p

Использовать небольшую рамку для заголовка.

-t⟨float⟩

Обычно элементы трассировки, сумма которых составляет менее 1% профиля, удаляются из профиля. Параметр -t позволяет изменить этот процент (максимум 5%).

-t0 запрашивает не удалять элементы трассировки из профиля, гарантируя, что все данные будут отображены.

-c

Генерировать цветной вывод.

-y

Игнорировать метки.

-?

Вывести информацию об использовании.

8.5.1. Обработка файла hp

(Замечания любезно предоставлены Ян-Виллемом Массеном.)

Файл FOO.hp , полученный при запросе профиля кучи программы FOO , является текстовым файлом с особенно простой структурой. Вот пример, в котором большая часть фактических данных опущена:

JOB "FOO -hC"
DATE "Thu Dec 26 18:17 2002"
SAMPLE_UNIT "seconds"
VALUE_UNIT "bytes"
BEGIN_SAMPLE 0.00
END_SAMPLE 0.00
BEGIN_SAMPLE 15.07
  ... sample data ...
END_SAMPLE 15.07
BEGIN_SAMPLE 30.23
  ... sample data ...
END_SAMPLE 30.23
... etc.
BEGIN_SAMPLE 11695.47
END_SAMPLE 11695.47

Первые четыре строки (JOB, DATE, SAMPLE_UNIT, VALUE_UNIT) образуют заголовок. Каждый блок строк, начинающийся с BEGIN_SAMPLE и заканчивающийся END_SAMPLE, образует один образец (можно представить это как вертикальный срез профиля кучи). Утилита hp2ps должна принимать любой ввод с правильно отформатированным заголовком, за которым следуют ряд *полных* образцов.

8.5.2. Увеличение масштаба областей вашего профиля

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

8.5.3. Просмотр профиля кучи работающей программы

Файл .hp генерируется по частям по мере выполнения вашей программы. Теоретически, выполнение hp2ps на неполном файле должно создать снимок использования кучи вашей программы. Однако последний образец в файле может быть неполным, что приведет к ошибке hp2ps. Если вы используете машину с установленными утилитами UNIX, решить эту проблему несложно (хотя получившаяся командная строка выглядит довольно сложной):

head -`fgrep -n END_SAMPLE FOO.hp | tail -1 | cut -d : -f 1` FOO.hp \
    | hp2ps > FOO.ps

Команда fgrep -n END_SAMPLE FOO.hp находит конец каждого полного образца в файле FOO.hp, и помечает каждый образец номером его заключительной строки. Затем мы выбираем номер строки последнего полного образца с помощью tail и cut. Это используется как параметр для head; результат эквивалентен удалению последнего неполного образца из файла FOO.hp. В результате получается правильно отформатированный файл .hp, который мы передаем напрямую в hp2ps.

8.5.4. Просмотр профиля кучи в реальном времени

Программы gv и ghostview имеют опцию «отслеживание файла», которая может использоваться для просмотра актуального профиля кучи вашей программы по мере её выполнения. Просто сгенерируйте частичный профиль кучи, как описано в предыдущем разделе. Запустите gv на вашем профиле:

gv -watch -orientation=seascape FOO.ps

Если вы забудете флаг -watch, вы всё равно можете выбрать «Отслеживать файл» из меню «Состояние». Теперь каждый раз, когда вы генерируете новый профиль FOO.ps, отображение будет обновляться автоматически.

Всё это можно заключить в небольшой скрипт:

#!/bin/sh
head -`fgrep -n END_SAMPLE FOO.hp | tail -1 | cut -d : -f 1` FOO.hp \
  | hp2ps > FOO.ps
gv -watch -orientation=seascape FOO.ps &
while [ 1 ] ; do
  sleep 10 # We generate a new profile every 10 seconds.
  head -`fgrep -n END_SAMPLE FOO.hp | tail -1 | cut -d : -f 1` FOO.hp \
    | hp2ps > FOO.ps
done

Иногда gv зависает при попытке прочитать неполную копию файла FOO.ps (потому что hp2ps всё ещё работает в процессе обновления). Немного более сложный скрипт обходит эту проблему, используя тот факт, что отправка сигнала SIGHUP в gv заставит его перечитать входной файл:

#!/bin/sh
head -`fgrep -n END_SAMPLE FOO.hp | tail -1 | cut -d : -f 1` FOO.hp \
  | hp2ps > FOO.ps
gv FOO.ps &
gvpsnum=$!
while [ 1 ] ; do
  sleep 10
  head -`fgrep -n END_SAMPLE FOO.hp | tail -1 | cut -d : -f 1` FOO.hp \
    | hp2ps > FOO.ps
  kill -HUP $gvpsnum
done

8.6. Профилирование параллельных и конкурирующих программ

Сочетание -threaded и -prof вполне допустимо, и, действительно, возможно профилировать программу, работающую на нескольких процессорах с опцией RTS -N ⟨x⟩. 3

Однако, существуют некоторые ограничения. В текущей реализации профилируемая программа, вероятно, будет масштабироваться гораздо хуже, чем непрофилируемая программа, поскольку реализация профилирования использует некоторые общие структуры данных, которые требуют блокировки в системе времени выполнения. Кроме того, статистические данные об распределении памяти, собираемые профилируемой программой, хранятся в общей памяти, но *не* блокируются (для скорости), что означает, что эти данные могут быть неточными для параллельных программ.

Мы настоятельно рекомендуем использовать -fno-prof-count-entries при компиляции программы для профилирования на нескольких ядрах, потому что счётчики записей также хранятся в общей памяти, а их непрерывное обновление на нескольких ядрах крайне медленное.

Мы также рекомендуем использовать ThreadScope для профилирования параллельных программ; он предлагает графический интерфейс для визуализации параллельного выполнения и дополняет функции профилирования времени и пространства, предоставляемые GHC.

8.7. Наблюдение за покрытием кода

Инструменты для анализа покрытия кода позволяют программисту определить, какие части их кода были фактически выполнены, а какие никогда не были вызваны. GHC имеет опцию для генерации инструментированного кода, который записывает информацию о покрытии кода в составе набора инструментов Haskell Program Coverage (HPC), входящего в GHC. Инструменты HPC могут использоваться для отображения сгенерированной информации о покрытии кода в удобочитаемой форме.

Правильно инструментированный код предоставляет информацию о покрытии двух типов: покрытие исходного кода и покрытие логических условий. Покрытие исходного кода — это степень, в которой использовалась каждая часть программы, измеряемая на трех разных уровнях: объявления (как верхнего уровня, так и локальные), варианты (среди нескольких уравнений или ветвей выбора) и выражения (на каждом уровне). Покрытие логических условий — это степень, в которой каждое из значений True и False получено в каждом синтаксическом контексте булевой переменной (т. е. в условии, в условии выбора, в квалификаторе).

HPC отображает оба вида информации двумя основными способами: текстовые отчеты со статистическими данными (hpc report) и исходные файлы с цветовой разметкой (hpc markup). Для покрытия логических условий существует четыре возможных результата для каждого условия, условия выбора или квалификатора: оба значения True и False встречаются; только True; только False; не вычислено. В выводе hpc-markup выделение жёлтым фоном указывает часть программы, которая не была никогда вычислена; зелёный фон указывает выражение, всегда равное True, а красный фон — выражение, всегда равное False.

8.7.1. Небольшой пример: Вычисление обратных величин

В качестве примера у нас есть программа под названием Recip.hs, которая вычисляет точные десятичные представления обратных величин, при этом повторяющиеся части указаны в скобках.

reciprocal :: Int -> (String, Int)
reciprocal n | n > 1 = ('0' : '.' : digits, recur)
             | otherwise = error
              "attempting to compute reciprocal of number <= 1"
  where
  (digits, recur) = divide n 1 []
divide :: Int -> Int -> [Int] -> (String, Int)
divide n c cs | c `elem` cs = ([], position c cs)
              | r == 0      = (show q, 0)
              | r /= 0      = (show q ++ digits, recur)
  where
  (q, r) = (c*10) `quotRem` n
  (digits, recur) = divide n r (c:cs)

position :: Int -> [Int] -> Int
position n (x:xs) | n==x      = 1
                  | otherwise = 1 + position n xs

showRecip :: Int -> String
showRecip n =
  "1/" ++ show n ++ " = " ++
  if r==0 then d else take p d ++ "(" ++ drop p d ++ ")"
  where
  p = length d - r
  (d, r) = reciprocal n

main = do
  number <- readLn
  putStrLn (showRecip number)
  main

Инструментирование HPC включено с помощью флага -fhpc:

$ ghc -fhpc Recip.hs

GHC создаёт подкаталог .hpc в текущем каталоге и помещает в него файлы индексов HPC (.mix) — по одному на каждый скомпилированный модуль. Вам не нужно беспокоиться об этих файлах: они содержат информацию, необходимую инструменту hpc для генерации данных о покрытии для скомпилированных модулей после выполнения программы.

$ ./Recip
1/3
= 0.(3)

Запуск программы создаёт файл с суффиксом .tix, в данном случае Recip.tix, который содержит данные о покрытии для этого запуска программы. Программа может запускаться несколько раз (например, с различными тестовыми данными), и данные о покрытии от отдельных запусков накапливаются в файле .tix. Чтобы сбросить данные о покрытии и начать заново, просто удалите файл .tix. Вы можете управлять местом генерации файла .tix с помощью переменной среды HPCTIXFILE.

HPCTIXFILE

Устанавливает путь к выводу файла HPC .tix.

После запуска программы мы можем сгенерировать текстовый отчёт о покрытии:

$ hpc report Recip
 80% expressions used (81/101)
 12% boolean coverage (1/8)
      14% guards (1/7), 3 always True,
                        1 always False,
                        2 unevaluated
       0% 'if' conditions (0/1), 1 always False
     100% qualifiers (0/0)
 55% alternatives used (5/9)
100% local declarations used (9/9)
100% top-level declarations used (5/5)

Мы также можем сгенерировать отформатированную версию исходного кода.

$ hpc markup Recip
writing Recip.hs.html

Это создает один файл на каждый модуль Haskell и 4 файла-индекса hpc_index.html, hpc_index_alt.html, hpc_index_exp.html, hpc_index_fun.html.

8.7.2. Опции для инструментирования кода для анализа покрытия

-fhpc

Включить анализ покрытия кода для текущего модуля или модулей, которые компилируются.

Модули, скомпилированные с этой опцией, могут быть свободно смешаны с модулями, скомпилированными без неё; на самом деле, большинство библиотек обычно компилируются без -fhpc. При запуске программы данные о покрытии будут генерироваться только для тех модулей, которые были скомпилированы с -fhpc, и инструмент hpc будет отображать информацию только о этих модулях.

8.7.3. Набор инструментов hpc

Команда hpc имеет несколько подкоманд:

$ hpc
Usage: hpc COMMAND ...

Commands:
  help        Display help for hpc or a single command
Reporting Coverage:
  report      Output textual report about program coverage
  markup      Markup Haskell source with program coverage
Processing Coverage files:
  sum         Sum multiple .tix files in a single .tix file
  combine     Combine two .tix files in a single .tix file
  map         Map a function over a single .tix file
Coverage Overlays:
  overlay     Generate a .tix file from an overlay file
  draft       Generate draft overlay that provides 100% coverage
Others:
  show        Show .tix file in readable, verbose format
  version     Display version for hpc

В общем случае, эти опции действуют на файл .tix после того, как инструментированный бинарник сгенерировал его.

Инструмент hpc предполагает, что вы находитесь в корневом каталоге расположения приложения, а файл .tix находится в том же корневом каталоге. Вы можете использовать флаг --srcdir для использования hpc для любого другого каталога, и использовать --srcdir многократно для анализа программ, скомпилированных из разных мест, как это обычно делается для пакетов.

Теперь мы более подробно объясним основные режимы работы hpc.

8.7.3.1. hpc report

hpc report генерирует текстовый отчет о покрытии. По умолчанию все модули и пакеты учитываются при генерации отчёта, если не используются include или exclude. Отчет представляет собой сводку, если не используется флаг --per-module. Опция --xml-output позволяет инструментам использовать hpc для получения данных о покрытии.

$ hpc help report
Usage: hpc report [OPTION] .. <TIX_FILE> [<MODULE> [<MODULE> ..]]

Options:

    --per-module                  show module level detail
    --decl-list                   show unused decls
    --exclude=[PACKAGE:][MODULE]  exclude MODULE and/or PACKAGE
    --include=[PACKAGE:][MODULE]  include MODULE and/or PACKAGE
    --srcdir=DIR                  path to source directory of .hs files
                                  multi-use of srcdir possible
    --hpcdir=DIR                  append sub-directory that contains .mix files
                                  default .hpc [rarely used]
    --reset-hpcdirs               empty the list of hpcdir's
                                  [rarely used]
    --xml-output                  show output in XML

8.7.3.2. hpc markup

hpc markup размечает исходные файлы цветным HTML.

$ hpc help markup
Usage: hpc markup [OPTION] .. <TIX_FILE> [<MODULE> [<MODULE> ..]]

Options:

    --exclude=[PACKAGE:][MODULE]  exclude MODULE and/or PACKAGE
    --include=[PACKAGE:][MODULE]  include MODULE and/or PACKAGE
    --srcdir=DIR                  path to source directory of .hs files
                                  multi-use of srcdir possible
    --hpcdir=DIR                  append sub-directory that contains .mix files
                                  default .hpc [rarely used]
    --reset-hpcdirs               empty the list of hpcdir's
                                  [rarely used]
    --fun-entry-count             show top-level function entry counts
    --highlight-covered           highlight covered code, rather that code gaps
    --destdir=DIR                 path to write output to

8.7.3.3. hpc sum

hpc sum складывает любое количество файлов .tix в один файл .tix. hpc sum не изменяет исходный файл .tix; он создает новый файл .tix.

$ hpc help sum
Usage: hpc sum [OPTION] .. <TIX_FILE> [<TIX_FILE> [<TIX_FILE> ..]]
Sum multiple .tix files in a single .tix file

Options:

    --exclude=[PACKAGE:][MODULE]  exclude MODULE and/or PACKAGE
    --include=[PACKAGE:][MODULE]  include MODULE and/or PACKAGE
    --output=FILE                 output FILE
    --union                       use the union of the module namespace (default is intersection)

8.7.3.4. hpc combine

hpc combine — это универсальный инструмент для hpc. Он может использоваться для вычисления разницы между .tix файлами, вычитания одного .tix файла из другого или сложения двух .tix файлов. hpc combine не изменяет исходный .tix файл; он генерирует новый .tix файл.

$ hpc help combine
Usage: hpc combine [OPTION] .. <TIX_FILE> <TIX_FILE>
Combine two .tix files in a single .tix file

Options:

    --exclude=[PACKAGE:][MODULE]  exclude MODULE and/or PACKAGE
    --include=[PACKAGE:][MODULE]  include MODULE and/or PACKAGE
    --output=FILE                 output FILE
    --function=FUNCTION           combine .tix files with join function, default = ADD
                                  FUNCTION = ADD | DIFF | SUB
    --union                       use the union of the module namespace (default is intersection)

8.7.3.5. hpc map

hpc map инвертирует или обнуляет .tix файл. hpc map не изменяет исходный .tix файл; он генерирует новый .tix файл.

$ hpc help map
Usage: hpc map [OPTION] .. <TIX_FILE>
Map a function over a single .tix file

Options:

    --exclude=[PACKAGE:][MODULE]  exclude MODULE and/or PACKAGE
    --include=[PACKAGE:][MODULE]  include MODULE and/or PACKAGE
    --output=FILE                 output FILE
    --function=FUNCTION           apply function to .tix files, default = ID
                                  FUNCTION = ID | INV | ZERO
    --union                       use the union of the module namespace (default is intersection)

8.7.3.6. hpc overlay и hpc draft

Наложения — это экспериментальная функция HPC, текстовое описание покрытия. hpc draft используется для генерации чернового наложения из файла .tix, а hpc overlay генерирует файлы .tix из наложения.

% hpc help overlay
Usage: hpc overlay [OPTION] .. <OVERLAY_FILE> [<OVERLAY_FILE> [...]]

Options:

    --srcdir=DIR   path to source directory of .hs files
                   multi-use of srcdir possible
    --hpcdir=DIR                  append sub-directory that contains .mix files
                                  default .hpc [rarely used]
    --reset-hpcdirs               empty the list of hpcdir's
                                  [rarely used]
    --output=FILE  output FILE
% hpc help draft
Usage: hpc draft [OPTION] .. <TIX_FILE>

Options:

    --exclude=[PACKAGE:][MODULE]  exclude MODULE and/or PACKAGE
    --include=[PACKAGE:][MODULE]  include MODULE and/or PACKAGE
    --srcdir=DIR                  path to source directory of .hs files
                                  multi-use of srcdir possible
    --hpcdir=DIR                  append sub-directory that contains .mix files
                                  default .hpc [rarely used]
    --reset-hpcdirs               empty the list of hpcdir's
                                  [rarely used]
    --output=FILE                 output FILE

8.7.4. Ограничения и недостатки покрытия кода Haskell

HPC не пытается заблокировать .tix файл, поэтому несколько одновременно работающих бинарников в одном каталоге могут столкнуться с проблемой гонки. Во время компиляции невозможно изменить имя генерируемого .tix файла; во время выполнения имя генерируемого .tix файла может быть изменено с помощью HPCTIXFILE; имя .tix файла также изменится, если вы переименуете бинарник. HPC не работает с GHCi.

8.8. Использование профилирования «ticky-ticky» (для разработчиков)

-ticky

Включить профилирование ticky-ticky.

Поскольку профилирование ticky-ticky требует определенной осведомленности об внутренних деталях GHC, мы переместили документацию на вики-сайт разработчиков GHC. Ознакомьтесь с его обзором вариантов профилирования, который включает ссылку на страницу профилирования ticky-ticky.

1

-fprof-auto ранее назывался -auto-all до GHC 7.4.1.

2

Обратите внимание, что эта политика немного изменилась в GHC 7.4.1 по сравнению с предыдущими версиями и, возможно, изменится еще, ваши отзывы приветствуются.

3

Эта функция была добавлена в GHC 7.4.1.

© 2002–2007 The University Court of the University of Glasgow. All rights reserved.
Licensed under the Glasgow Haskell Compiler License.
https://downloads.haskell.org/~ghc/8.10.2/docs/html/users_guide/profiling.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API