haskell-notes

criterion.

Она проводит серию тестов и по ним оценивает показатели быстродействия. При этом учитывается до-

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

вается слишком большим, программа сообщает нам: что-то тут не чисто, данным не стоит доверять. Более

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

показателей.

284 | Глава 19: Ориентируемся по карте

Основные типы criterion

Центральным элементом бибилиотеки является класс Benchmarkable. Он объединяет данные, которые

можно тестировать. Среди них чистые функции (тип Pure) и значения с побочными эффектами (тип IO a).

Мы можем превращать данные в тесты (тип Benchmark) с помощью функции bench:

benchSource :: Benchmarkable b => String -> b -> Benchmark

Она добавляет к данным комментарий и превращает их в тесты. Как было отмечено, существует одна

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

бой, поэтому для независимого тестирования мы оборачиваем функции в специальный тип Pure. У нас есть

два варианта тестирования:

Мы можем протестировать приведение результата к заголовочной нормальной форме (вспомните главу

о ленивых вычислениях):

nf :: NFData b => (a -> b) -> a -> Pure

или к слабой заголовочной нормальной форме:

whnf :: (a -> b) -> a -> Pure

Аналогичные функции (nfIO, whnfIO) есть и для данных с побочными эффектами. Класс NFData обозна-

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

теку criterion из библиотеки deepseq. Стоит отметить эту бибилотеку. В ней определён аналог функции

seq. Функция seq приводит значения к слабой заголовочной нормальной форме (мы заглядываем вглюбь

значения лишь на один конструктор), а функция deepseq проводит полное вычисление значения. Значение

приводится к заголовочной нормальной форме.

Также нам пригодится функция группировки тестов:

bgroup :: String -> [Benchmark] -> Benchmark

С её помощью мы объединяем список тестов в один, под некоторым именем. Тестирование проводится с

помощью функции defaultMain:

defaultMain :: [Benchmark] -> IO ()

Она принимает список тестов и выполняет их. Выполнение тестов заключается в компиляции програм-

мы. После компиляции мы получим исполняемый файл который проводит тестирование в зависимости от

параметров, указываемых фланами. До них мы ещё доберёмся, а пока опишем наши тесты:

— | Module: Speed.hs

module Main where

import Criterion.Main

import Control.DeepSeq

import Metro

instance NFData Station where

rnf (St a b) = rnf (rnf a, rnf b)

instance NFData Way

where

instance NFData Name where

pair1 = (St Orange DnoBolota, St Green Prizrak)

pair2 = (St Red Lao, St Blue De)

test name search = bgroup name $ [

bench ”1” $ nf (uncurry search) pair1,

bench ”2” $ nf (uncurry search) pair2]

main = defaultMain [

test ”Set”

connectSet,

test ”Hash” connectHashSet]

Оценка быстродействия с помощью criterion | 285

Экземпляр для класса NFData похож на экземпляр для Hashable. Мы также определили метод значения

через методы для типов, из которых он состоит. Класс NFData устроен так, что для типов из класса Enum мы

можем воспользоваться определением по умолчанию (как в случае для Way и Name).

Теперь перейдём в командную строку, переключимся на директорию с нашим модулем и скомпилируем

его:

$ ghc -O —make Speed.hs

Флаг -O говорит ghc, что не обходимо провести оптимизацию кода. Появится исполняемый файл Speed.

Что мы можем делать с этим файлом? Узнать это можно, запустив его с флагом –help:

Мы можем узнать какие функции нам доступны, набрав:

$ ./Speed —help

I don’t know what version I am.

Usage: Speed [OPTIONS] [BENCHMARKS]

-h, -?

—help

print help, then exit

-G

—no-gc

do not collect garbage between iterations

-g

—gc

collect garbage between iterations

-I CI

—ci=CI

bootstrap confidence interval

-l

—list

print only a list of benchmark names

-o FILENAME

—output=FILENAME

report file to write to

-q

—quiet

print less output

—resamples=N

number of bootstrap resamples to perform

-s N

—samples=N

number of samples to collect

-t FILENAME

—template=FILENAME

template file to use

-u FILENAME

—summary=FILENAME

produce a summary CSV file of all results

-V

—version

display version, then exit

-v

—verbose

print more output

If no benchmark names are given, all are run

Otherwise, benchmarks are run by prefix match

Из этих настроек самые интресные, это s и o. s указывает число сэмплов выборке (столько раз будет

запущен каждый тест). а o говорит, о том в какой файл поместить результаты. Результаты представлены в

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

(например для отчёта) можно с помощью флага u.

Проверим результаты:

./Speed —o res. html s 100

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

ритм работал немного лучше. Но выборку из двух вариантов вряд ли можно считать убедительной. Давайте

расширим выборку с помощью QuickCheck. Мы запустим проверку какого-нибудь свойства тем и другим

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