Изменения

Перейти к: навигация, поиск

R:Измерение времени выполнения выражений

1088 байтов убрано, 12:56, 13 февраля 2014
м
Нет описания правки
== Функция system.time() ==
Самый простой инструмент для измерения времени выполнения кода - функция {{Inline-<code|>system.time()|lang="rsplus"}} </code> из пакета {{Inline-<code|>base|lang="rsplus"}}</code>. В качестве аргумента функция {{Inline-<code|>system.time()|lang="rsplus"}} </code> принимает выражения и возвращает время выполнения данного выражения. Измерим время выполнения функции {{Inline-<code|>Sys.sleep()|lang="rsplus"}}</code>, которая останавливает выполнение кода на заданный интервал времени (в секундах):
<syntaxhighlight lang="rsplus">
Как видим, на выполнение данной операции заняло ровно одну секунду.
Приведём ещё один пример. Сравним время вычисления встроенной в R функции {{Inline-<code|>mean()|lang="rsplus"}} </code> и среднего, вычисленного по формуле <math>\frac{1}{n}\sum_{i=1}^{n}x_{i}</math>. на сгенерированном массиве нормально распределенных значений:
<syntaxhighlight lang="rsplus">
* elapsed - реальное время, которое заняло выполнение команды.
Соответственно, в выводе функции нас интересует значение {{Inline-<code|>elapsed|lang="rsplus"}}</code>, который показывает время выполнения функции (выражения) в секундах. Как мы видим, внешне более сложное выражение {{Inline-<code|>sum(x) / length(x)|lang="rsplus"}} </code> выполняется быстрее стандартной функции {{Inline-<code|>mean(x)|lang="rsplus"}}</code>.
К сожалению, подобный способ достаточно ненадежен, так как для оценки времени выполнения выражения функция {{Inline-<code|>system.time()|lang="rsplus"}} </code> обращается к системным значениям времени. Следовательно, если во время выполнения кода параллельно производятся и другие операции на компьютере (а такое случается практически в ста процентах случаев), то возможно увеличение времени выполнения R-кода. Некоторую вариативность результатов можно увидеть, даже если выполнить функцию {{Inline-<code|>system.time()|lang="rsplus"}} </code> несколько раз подряд. Подобной неточности оценки можно избежать путём многократного повторения выполняемых выражений и вычислением среднего времени, что позволит сгладить часть вариаций.
Проиллюстрируем вышесказанное на примере:
</syntaxhighlight>
В этом примере с помощью функции {{Inline-<code|>replicate()|lang="rsplus"}} </code> мы повторили выражение {{Inline-<code|>system.time(mean(x))|lang="rsplus"}} </code> 10 раз, отфильтровав вывод функции {{Inline-<code|>system.time()|lang="rsplus"}} </code> так, чтобы нам выводилось только время выполнения команды, дописав {{Inline-<code|>[["elapsed"]]|lang="rsplus"}}</code>. Как мы видим, время выполнения при повторном выполнении выражения может отличаться.
Базовый пакет позволяет реализовать процедуру многократного повторения выражения функции как минимум двумя способами. Первый - функция {{Inline-<code|>replicate()|lang="rsplus"}}</code>. Приведенное выше сопоставление времени выполнения двух выражений при использовании функции {{Inline-<code|>replicate()|lang="rsplus"}} </code> будет выглядеть следующим образом:
<syntaxhighlight lang="rsplus">
</syntaxhighlight>
Тот же самый эффект можно получить и с помощью обычного цикла {{Inline-<code|>for()|lang="rsplus"}}</code>:
<syntaxhighlight lang="rsplus">
</syntaxhighlight>
В примере выше мы взяли только значения {{Inline-<code|>elapsed|lang="rsplus"}} </code> и рассчитали медиану <ref>Медиана является более устойчивой мерой центральной тенденции при асимметрии распределения, что, как правило, характерно для измерения времени.</ref>.
Вместо подобных решений можно использовать специальные пакеты, предназначенные для измерения производительности кода, в частности, пакеты {{Inline-<code|>rbenchmark|lang="rsplus"}} </code> и {{Inline-<code|>microbenchmark|lang="rsplus"}}</code>. Основной принцип работы этих пакетов заключается в многократном выполнении выражений и расчёта ряда интегральных показателей, в частности, суммы, среднего значения или медианы времени выполнения всех попыток.
== Пакет rbenchmark ==
Основа пакета {{Inline-<code|>rbenchmark|lang="rsplus"}} </code> - функция {{Inline-<code|>benchmark()|lang="rsplus"}}</code>. Данная функция работает следующим образом: указанные в качестве аргументов выражения выполняются заданное количество раз (по умолчанию 100) и вычисляется время, затраченное на выполнение всех попыток. В качестве аргументов функции {{Inline-<code|>benchmark()|lang="rsplus"}} </code> необходимо передать выражения или функции, а также количество повторений, передаваемых аргументом replications<ref>Анализ функции {{Inline-<code|>benchmark()|lang="rsplus"}} </code> показал, что, данная функция использует {{Inline-<code|>system.time()|lang="rsplus"}} </code> и {{Inline-<code|>replicate()|lang="rsplus"}}</code>, рассмотренные в предыдущем разделе.</ref>.
Для примера возьмём несколько способов расчёта среднего арифметического для сгенерированного массива данных.
</syntaxhighlight>
Использованные нами способы - функции векторизованных вычислений ({{Inline-<code|>apply()|lang="rsplus"}}</code>, {{Inline-<code|>vapply()|lang="rsplus"}}</code>), стандартный цикл и специальная функция вычисления средних по столбцам {{Inline-<code|>ColMeans()|lang="rsplus"}}</code>. Представим эти способы в виде самостоятельных функций для удобства их вызова при работе с {{Inline-<code|>benchmark()|lang="rsplus"}}</code>:
<syntaxhighlight lang="rsplus">
</syntaxhighlight>
Убедимся, что функции возвращают одинаковый результат. Сделать это можно с помощью функций {{Inline-<code|>identical()|lang="rsplus"}} </code> или {{Inline-<code|>all.equal()|lang="rsplus"}}</code>:
<syntaxhighlight lang="rsplus">
</syntaxhighlight>
Наиболее важны для нас в выводе функции {{Inline-<code|>benchmark()|lang="rsplus"}} </code> столбцы {{Inline-<code|>elapsed|lang="rsplus"}} </code> и {{Inline-<code|>relative|lang="rsplus"}}</code>. Столбец {{Inline-<code|>elapsed|lang="rsplus"}} </code> показывает время в секундах, затраченное на выполнение интересующей нас функции. Как видим из примера, самыми медленными оказались функции {{Inline-<code|>colMeansApply()|lang="rsplus"}} </code> и {{Inline-<code|>colMeansLoop()|lang="rsplus"}}</code>, а самой быстрой {{Inline-<code|>colMeans()|lang="rsplus"}}</code>, причём превосходит остальные по скорости выполнения как минимум в 7 раз.
Показатель {{Inline-<code|>relative|lang="rsplus"}} </code> дает информацию о разнице во времени относительно самого быстрого выражения (в нашем случае это {{Inline-<code|>ColMeans()|lang="rsplus"}}</code>), т.е. время самого быстрого выражения берётся за единицу, и рассчитывается относительное время для остальных выражений.
Для более удобного просмотра можно отфильтровать вывод функции {{Inline-<code|>benchmark()|lang="rsplus"}} </code> с помощью аргумента {{Inline-<code|>columns|lang="rsplus"}}</code>. Также может быть полезен аргумент {{Inline-<code|>order|lang="rsplus"}}</code>, позволяющий отсортировать вывод по любому из столбцов. Для примера зададим набор показателей, которые мы хотим включить в таблицу (в данном случае это «test», «replications», «elapsed», «relative»), и отсортируем выдачу по столбцу «elapsed» по возрастанию значений:
<syntaxhighlight lang="rsplus">
Таким образом, сравнив несколько альтернатив решения нашей задачи, мы можем сделать обоснованный выбор в пользу наиболее эффективного варианта.
Чтобы не указывать нужные столбцы каждый раз, когда используется функция {{Inline-<code|>benchmark()|lang="rsplus"}}</code>, можно закрепить заданный формат выдачи результатов (далее используется именно такой формат вывода, с сортировкой по столбцу «relative»). Для этого следует воспользоваться функцией {{Inline-<code|>formals()|lang="rsplus"}}</code>:
<syntaxhighlight lang="rsplus">
== Пакет microbenchmark ==
Функция {{Inline-<code|>microbenchmark()|lang="rsplus"}} </code> одноименного пакета работает сходным с функцией {{Inline-<code|>benchmark()|lang="rsplus"}} </code> образом, но предоставляет более гибкие средства по управлению процессом выполнения выражений<ref>Но в отличии от функции benchmark() использует собственную реализацию измерения времени выполнения и организацию повторных испытаний.</ref>. Особенностями реализованных в пакете {{Inline-<code|>microbenchmark|lang="rsplus"}} </code> являются:
* Возможность измерения времени выполнения выражения вплоть до наносекунд;
* Возможность проведения предварительных испытаний до начала процесса измерений.
Также с помощью функции {{Inline-<code|>microbenchmark()|lang="rsplus"}} </code> можно получить исходную информацию о времени выполнения каждой попытки, что даёт достаточно широкие возможности по обработке и анализу полученных результатов. В таблице ниже представлено время выполнения пяти функций вычисления среднего значения из предыдущего примера, полученное с помощью функции {{Inline-<code|>microbenchmark()|lang="rsplus"}}</code>:
<syntaxhighlight lang="rsplus">
Все результаты представлены в виде описательных статистик, рассчитанных из времени выполнения каждой попытки. Наиболее информативный столбец - это столбец median, который показывает медиану времени выполнения выражения для всех попыток.
Вся полученная информация о попытках применения функций вычисления средних записана в отдельную переменную {{Inline-<code|>res|lang="rsplus"}}</code>. С помощью функции {{Inline-<code|>str()|lang="rsplus"}} </code> можно увидеть структуру переменной:
<syntaxhighlight lang="rsplus">
</syntaxhighlight>
Переменная {{Inline-<code|>res|lang="rsplus"}}</code>, как можно увидеть в выводе функции {{Inline-<code|>str()|lang="rsplus"}}</code>, представляет собой список (list) и включает в себя две переменные: {{Inline-<code|>expr|lang="rsplus"}} </code> (выражение) и {{Inline-<code|>time|lang="rsplus"}} </code> (время выполнения). На основе этой информации и рассчитываются описательные статистики, приведённые в примере применения функции {{Inline-<code|>microbenchmark()|lang="rsplus"}}</code>. Наличие исходных данных о каждой попытке позволяет самостоятельно выбирать, рассчитывать и сравнивать предпочтитаемые показатели. Например, расчет медианного времени выполнения попытки и общего времени выполнения всех попыток для каждого выражения выглядит следующим образом:
<syntaxhighlight lang="rsplus">
Умножение на <math>10^{-6}</math> --- это перевод в миллисекунды. Чтобы получить секунды, нужно, соответственно, умножить на <math>10^{-9}</math>.
Помимо настройки формата вывода, выбора показателей, наличие информации о времени выполнения выражения в каждой попытке позволяет визуализировать результаты оценки времени выполнения выражения. Например, с помощью функции {{Inline-<code|>autoplot()|lang="rsplus"}} </code> из пакета {{Inline-<code|>ggplot2|lang="rsplus"}}</code>, можно получить следующий график:
<syntaxhighlight lang="rsplus">
[[Файл:Microbenchmark-autoplot-colMeans.png|600px|центр]]
Ещё один довольно интересный способ графического представления результатов измерения скорости выполнения кода с помощью функции {{Inline-<code|>qplot()|lang="rsplus"}} </code> представлен ниже:
<syntaxhighlight lang="rsplus">
[[Файл:Microbenchmark-dotplot-colMeans.png|600px|центр]]
Так же можно, если возникнет необходимость, оценить статистическую значимость различий во времени выполнения выражений. Благодаря тому, что в переменной {{Inline-<code|>res|lang="rsplus"}} </code> хранятся данные о времени выполнения каждой попытки из заданного числа, становится возможным использование статистических критериев. Выбор критерия - на усмотрение аналитика, в примере ниже использовался параметрический критерий сравнения групп t-Стьюдента с поправкой уровня статистической значимости Холма для множественных сравнений:
<syntaxhighlight lang="rsplus">
</syntaxhighlight>
Из вышеприведённого вывода видно, что скорость выполнения всех функций статистически значимо разлиается у всех функций, за исключением пары {{Inline-<code|>colMeansLoop(x)|lang="rsplus"}} - {{Inline</code> -<code|>colMeansVapply(x)|lang="rsplus"}} </code> (p-уровень = 0.62).
== Примечания ==

Навигация