Изменения

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

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

684 байта убрано, 13:08, 17 февраля 2014
м
Нет описания правки
Самый простой инструмент для измерения времени выполнения кода - функция <code>system.time()</code> из пакета <code>base</code>. В качестве аргумента функция <code>system.time()</code> принимает выражения и возвращает время выполнения данного выражения. Измерим время выполнения функции <code>Sys.sleep()</code>, которая останавливает выполнение кода на заданный интервал времени (в секундах):
<syntaxhighlight lang="rsplus">{{r-code|
> system.time(Sys.sleep(1))
пользователь система прошло
0.003 0.004 1.000
</syntaxhighlight>}}
Как видим, на выполнение данной операции заняло ровно одну секунду.
Приведём ещё один пример. Сравним время вычисления встроенной в R функции <code>mean()</code> и среднего, вычисленного по формуле <math>\frac{1}{n}\sum_{i=1}^{n}x_{i}</math>. на сгенерированном массиве нормально распределенных значений:
<syntaxhighlight lang="rsplus">{{r-code|
> x <- rnorm(10^7L)
> system.time(mean(x))
пользователь система прошло
0.013 0.000 0.012
</syntaxhighlight>}}
Функция возвращает 3 значения:
Проиллюстрируем вышесказанное на примере:
<syntaxhighlight lang="rsplus">{{r-code|
> replicate(10, system.time(mean(x))[["elapsed"]])
[1] 0.024 0.020 0.019 0.018 0.017 0.016 0.015 0.015 0.015 0.015
</syntaxhighlight>}}
В этом примере с помощью функции <code>replicate()</code> мы повторили выражение <code>system.time(mean(x))</code> 10 раз, отфильтровав вывод функции <code>system.time()</code> так, чтобы нам выводилось только время выполнения команды, дописав <code>[["elapsed"]]</code>. Как мы видим, время выполнения при повторном выполнении выражения может отличаться.
Базовый пакет позволяет реализовать процедуру многократного повторения выражения функции как минимум двумя способами. Первый - функция <code>replicate()</code>. Приведенное выше сопоставление времени выполнения двух выражений при использовании функции <code>replicate()</code> будет выглядеть следующим образом:
<syntaxhighlight lang="rsplus">{{r-code|
> system.time(replicate(100, mean(x)))
пользователь система прошло
пользователь система прошло
0.817 0.016 0.835
</syntaxhighlight>}}
Тот же самый эффект можно получить и с помощью обычного цикла <code>for()</code>:
<syntaxhighlight lang="rsplus">{{r-code|
> system.time(for (i in seq_len(100)) mean(x))
пользователь система прошло
пользователь система прошло
0.797 0.000 0.800
</syntaxhighlight>}}
Можно также использовать описательные статистики в сочетании с множественными повторениями:
<syntaxhighlight lang="rsplus">{{r-code|
> median(replicate(100, system.time(mean(x))[["elapsed"]]))
[1] 0.0155
</syntaxhighlight>}}
В примере выше мы взяли только значения <code>elapsed</code> и рассчитали медиану <ref>Медиана является более устойчивой мерой центральной тенденции при асимметрии распределения, что, как правило, характерно для измерения времени.</ref>.
Для примера возьмём несколько способов расчёта среднего арифметического для сгенерированного массива данных.
<syntaxhighlight lang="rsplus">{{r-code|
> x <- replicate(10, rnorm(10^6L))
</syntaxhighlight>}}
Использованные нами способы - функции векторизованных вычислений (<code>apply()</code>, <code>vapply()</code>), стандартный цикл и специальная функция вычисления средних по столбцам <code>ColMeans()</code>. Представим эти способы в виде самостоятельных функций для удобства их вызова при работе с <code>benchmark()</code>:
<syntaxhighlight lang="rsplus">{{r-code|
colMeansApply <- function(x) {
apply(x, 2, mean)
return(res)
}
</syntaxhighlight>}}
Убедимся, что функции возвращают одинаковый результат. Сделать это можно с помощью функций <code>identical()</code> или <code>all.equal()</code>:
<syntaxhighlight lang="rsplus">{{r-code|
> identical(colMeansApply(x), colMeansVapply(x), colMeansLoop(x), colMeansLoopVec(x), colMeans(x))
[1] TRUE
</syntaxhighlight>}}
Теперь, подключив пакет rbenchmark, мы можем сравнить время работы каждого из выбранных нами способов вычисления средних по столбцам:
<syntaxhighlight lang="rsplus">{{r-code|
> library(rbenchmark)
> benchmark(colMeansApply(x), colMeansVapply(x), colMeansLoop(x),
2 colMeansVapply(x) 100 7.724 9.827 7.630 0.067 0 0
5 colMeans(x) 100 0.786 1.000 0.784 0.000 0 0
</syntaxhighlight>}}
Наиболее важны для нас в выводе функции <code>benchmark()</code> столбцы <code>elapsed</code> и <code>relative</code>. Столбец <code>elapsed</code> показывает время в секундах, затраченное на выполнение интересующей нас функции. Как видим из примера, самыми медленными оказались функции <code>colMeansApply()</code> и <code>colMeansLoop()</code>, а самой быстрой <code>colMeans()</code>, причём превосходит остальные по скорости выполнения как минимум в 7 раз.
Для более удобного просмотра можно отфильтровать вывод функции <code>benchmark()</code> с помощью аргумента <code>columns</code>. Также может быть полезен аргумент <code>order</code>, позволяющий отсортировать вывод по любому из столбцов. Для примера зададим набор показателей, которые мы хотим включить в таблицу (в данном случае это «test», «replications», «elapsed», «relative»), и отсортируем выдачу по столбцу «elapsed» по возрастанию значений:
<syntaxhighlight lang="rsplus">{{r-code|
> benchmark(colMeansApply(x), colMeansVapply(x), colMeansLoop(x), colMeansLoopVec(x), colMeans(x),
+ replications = 100, order = "relative",
2 colMeansVapply(x) 100 7.716 9.867
1 colMeansApply(x) 100 13.142 16.806
</syntaxhighlight>}}
Таким образом, сравнив несколько альтернатив решения нашей задачи, мы можем сделать обоснованный выбор в пользу наиболее эффективного варианта.
Чтобы не указывать нужные столбцы каждый раз, когда используется функция <code>benchmark()</code>, можно закрепить заданный формат выдачи результатов (далее используется именно такой формат вывода, с сортировкой по столбцу «relative»). Для этого следует воспользоваться функцией <code>formals()</code>:
<syntaxhighlight lang="rsplus">{{r-code|
> formals(benchmark)$columns <- c("test", "replications", "elapsed", "relative")
> formals(benchmark)$order <- "relative"
</syntaxhighlight>}}
== Пакет <code>microbenchmark</code> ==
В таблице ниже представлено время выполнения пяти функций вычисления среднего значения из предыдущего примера, полученное с помощью функции <code>microbenchmark()</code>:
<syntaxhighlight lang="rsplus">{{r-code|
> res <- microbenchmark(colMeansApply(x), colMeansVapply(x), colMeansLoop(x),
+ colMeansLoopVec(x), colMeans(x), times = 100)
colMeansLoop(x) 75.632 76.920 85.65 90.346 99.707 100
colMeansApply(x) 102.908 127.148 132.24 136.095 144.833 100
</syntaxhighlight>}}
Все результаты представлены в виде описательных статистик, рассчитанных из времени выполнения каждой попытки. Наиболее информативный столбец - это столбец median, который показывает медиану времени выполнения выражения для всех попыток.
Вся полученная информация о попытках применения функций вычисления средних записана в отдельную переменную <code>res</code>. С помощью функции <code>str()</code> можно увидеть структуру переменной:
<syntaxhighlight lang="rsplus">{{r-code|
> str(res)
Classes ‘microbenchmark’ and 'data.frame': 500 obs. of 2 variables:
$ expr: Factor w/ 5 levels "colMeansApply(x)",..: 4 2 1 1 1 3 3 5 5 1 ...
$ time: num 1.02e+08 8.55e+07 1.04e+08 1.10e+08 1.29e+08 ...
</syntaxhighlight>}}
Переменная <code>res</code>, как можно увидеть в выводе функции <code>str()</code>, представляет собой список (list) и включает в себя две переменные: <code>expr</code> (выражение) и <code>time</code> (время выполнения). На основе этой информации и рассчитываются описательные статистики, приведённые в примере применения функции <code>microbenchmark()</code>. Наличие исходных данных о каждой попытке позволяет самостоятельно выбирать, рассчитывать и сравнивать предпочтитаемые показатели. Например, расчет медианного времени выполнения попытки и общего времени выполнения всех попыток для каждого выражения выглядит следующим образом:
<syntaxhighlight lang="rsplus">{{r-code|
> aggregate(time ~ expr, data = res, function(x) median(x) * 10^-6L)
expr time
4 colMeansLoopVec(x) 7576.8
5 colMeans(x) 788.8
</syntaxhighlight>}}
Умножение на <math>10^{-6}</math> --- это перевод в миллисекунды. Чтобы получить секунды, нужно, соответственно, умножить на <math>10^{-9}</math>.
Помимо настройки формата вывода, выбора показателей, наличие информации о времени выполнения выражения в каждой попытке позволяет визуализировать результаты оценки времени выполнения выражения. Например, с помощью функции <code>autoplot()</code> из пакета <code>ggplot2</code>, можно получить следующий график:
<syntaxhighlight lang="rsplus">{{r-code|
> library(ggplot2)
> autoplot(res)
</syntaxhighlight>}}
[[Файл:Microbenchmark-autoplot-colMeans.png|600px|центр]]
Ещё один довольно интересный способ графического представления результатов измерения скорости выполнения кода с помощью функции <code>qplot()</code> представлен ниже:
<syntaxhighlight lang="rsplus">{{r-code|
> qplot(y = time, data = res, colour = expr)
</syntaxhighlight>}}
[[Файл:Microbenchmark-dotplot-colMeans.png|600px|центр]]
Так же можно, если возникнет необходимость, оценить статистическую значимость различий во времени выполнения выражений. Благодаря тому, что в переменной <code>res</code> хранятся данные о времени выполнения каждой попытки из заданного числа, становится возможным использование статистических критериев. Выбор критерия - на усмотрение аналитика, в примере ниже использовался параметрический критерий сравнения групп t-Стьюдента с поправкой уровня статистической значимости Холма для множественных сравнений:
<syntaxhighlight lang="rsplus">{{r-code|
> pairwise.t.test(res$time, res$expr)
P value adjustment method: holm
</syntaxhighlight>}}
Из вышеприведённого вывода видно, что скорость выполнения всех функций статистически значимо разлиается у всех функций, за исключением пары <code>colMeansLoop(x)</code> - <code>colMeansVapply(x)</code> (p-уровень = 0.62).

Навигация