R:Измерение времени выполнения выражений — различия между версиями
м (→Функция system.time()) |
м (→Пакет rbenchmark) |
||
Строка 82: | Строка 82: | ||
== Пакет rbenchmark == | == Пакет rbenchmark == | ||
+ | |||
+ | Основа пакета {{Inline-code|rbenchmark|lang="rsplus"}} - функция {{Inline-code|benchmark()|lang="rsplus"}}. Данная функция работает следующим образом: указанные в качестве аргументов выражения выполняются заданное количество раз (по умолчанию 100) и вычисляется время, затраченное на выполнение всех попыток. В качестве аргументов функции {{Inline-code|benchmark()|lang="rsplus"}} необходимо передать выражения или функции, а также количество повторений, передаваемых аргументом replications<ref>Анализа функции {{Inline-code|benchmark()|lang="rsplus"}} показал, что, данная функция использует {{Inline-code|system.time()|lang="rsplus"}} и {{Inline-code|replicate()|lang="rsplus"}}, рассмотренные в предыдущем разделе.</ref>. | ||
+ | |||
+ | Для примера возьмём несколько способов расчёта среднего арифметического для сгенерированного массива данных. | ||
+ | |||
+ | Использованные нами способы - функции векторизованных вычислений ({{Inline-code|apply()|lang="rsplus"}}, {{Inline-code|vapply()|lang="rsplus"}}), стандартный цикл и специальная функция вычисления средних по столбцам {{Inline-code|ColMeans()|lang="rsplus"}}. Представим эти способы в виде самостоятельных функций для удобства их вызова при работе с {{Inline-code|benchmark()|lang="rsplus"}}: | ||
+ | |||
+ | <syntaxhighlight lang="rsplus"> | ||
+ | > x <- replicate(10, rnorm(10^6L)) | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | <syntaxhighlight lang="rsplus"> | ||
+ | CMApply <- function(x) { | ||
+ | apply(x, 2, mean) | ||
+ | } | ||
+ | |||
+ | CMVapply <- function(x) { | ||
+ | vapply(seq_len(ncol(x)), function(i) mean(x[, i]), FUN.VALUE = numeric(1)) | ||
+ | } | ||
+ | |||
+ | CMLoop <- function(x) { | ||
+ | n.vars <- ncol(x) | ||
+ | res <- double(n.vars) | ||
+ | for (i in seq_len(n.vars)) | ||
+ | res[i] <- mean(x[, i]) | ||
+ | return(res) | ||
+ | } | ||
+ | |||
+ | CMLoopVec <- function(x) { | ||
+ | n.vars <- ncol(x) | ||
+ | n <- nrow(x) | ||
+ | res <- double(n.vars) | ||
+ | for (i in seq_len(n.vars)) | ||
+ | res[i] <- sum(x[, i]) / n | ||
+ | return(res) | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Убедимся, что функции возвращают одинаковый результат. Сделать это можно с помощью функций {{Inline-code|identical()|lang="rsplus"}} или {{Inline-code|all.equal()|lang="rsplus"}}: | ||
+ | |||
+ | <syntaxhighlight lang="rsplus"> | ||
+ | > identical(CMApply(x), CMVapply(x), CMLoop(x), CMLoopVec(x), ColMeans(x)) | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Теперь, подключив пакет rbenchmark, мы можем сравнить время работы каждого из выбранных нами способов вычисления средних по столбцам: | ||
+ | |||
+ | <syntaxhighlight lang="rsplus"> | ||
+ | > library(rbenchmark) | ||
+ | > benchmark(CMApply(x), CMVapply(x), CMLoop(x), | ||
+ | CMLoopVec(x), ColMeans(x), replications = 100) | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Наиболее важны для нас в выводе функции {{Inline-code|benchmark()|lang="rsplus"}} столбцы elapsed и relative. Столбец elapsed показывает время, затраченное на выполнение интересующей нас функции. Как видим из примера, самыми медленными оказались функции {{Inline-code|CMApply()|lang="rsplus"}} и {{Inline-code|CMLoop()|lang="rsplus"}}, а самой быстрой {{Inline-code|ColMeans()|lang="rsplus"}}, причём превосходит остальные по скорости выполнения как минимум в 7 раз. | ||
+ | |||
+ | Показатель relative дает информацию о разнице во времени относительно самого быстрого выражения (в нашем случае это {{Inline-code|ColMeans()|lang="rsplus"}}), т.е. время самого быстрого выражения берётся за единицу, и рассчитывается относительное время для остальных выражений. | ||
+ | |||
+ | Для более удобного просмотра можно отфильтровать вывод функции {{Inline-code|benchmark()|lang="rsplus"}} с помощью аргумента columns. Также может быть полезен аргумент order, позволяющий отсортировать вывод по любому из столбцов. Для примера зададим набор показателей, которые мы хотим включить в таблицу (в данном случае это «test», «replications», «elapsed», «relative»), и отсортируем выдачу по столбцу «elapsed» по возрастанию значений: | ||
+ | |||
+ | <syntaxhighlight lang="rsplus"> | ||
+ | > benchmark(CMApply(x), CMVapply(x), CMLoop(x), CMLoopVec(x), ColMeans(x), | ||
+ | replications = 100, order = "relative", | ||
+ | columns = c("test", "replications", "elapsed", "relative")) | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Таким образом, сравнив несколько альтернатив решения нашей задачи, мы можем сделать обоснованный выбор в пользу наиболее эффективного варианта. | ||
+ | |||
+ | Чтобы не указывать нужные столбцы каждый раз, когда используется функция {{Inline-code|benchmark()|lang="rsplus"}}, можно закрепить заданный формат выдачи результатов (далее используется именно такой формат вывода, с сортировкой по столбцу «relative»). Для этого следует воспользоваться функцией {{Inline-code|formals()|lang="rsplus"}}: | ||
+ | |||
+ | <syntaxhighlight lang="rsplus"> | ||
+ | > formals(benchmark)$columns <- c("test", "replications", "elapsed", "relative") | ||
+ | > formals(benchmark)$order <- "relative" | ||
+ | </syntaxhighlight> | ||
== Пакет microbenchmark == | == Пакет microbenchmark == |
Версия 08:18, 23 января 2014
|
Материал «R:Измерение времени выполнения выражений», созданный автором Артём Клевцов, публикуется на условиях лицензии Creative Commons «Attribution» («Атрибуция») 4.0 Всемирная. | |
|
Для измерения времени выполнения выражений используют следующие инструменты:
- Функция Шаблон:Inline-code из базового пакета;
- Cпециализированные функции (benchmarks, бенчмарки);
- Функция профилирования времени выполнения кода Шаблон:Inline-code.
Функция system.time()
Самый простой инструмент для измерения времени выполнения кода - функция Шаблон:Inline-code из пакета Шаблон:Inline-code. В качестве аргумента функция Шаблон:Inline-code принимает выражения и возвращает время выполнения данного выражения. Измерим время выполнения функции Шаблон:Inline-code, которая останавливает выполнение кода на заданный интервал времени (в секундах):
<syntaxhighlight lang="rsplus"> > system.time(Sys.sleep(1)) пользователь система прошло
0.003 0.004 1.000
</syntaxhighlight>
Как видим, на выполнение данной операции заняло ровно одну секунду.
Приведём ещё один пример. Сравним время вычисления встроенной в R функции Шаблон:Inline-code и среднего, вычисленного по формуле [math]\frac{1}{n}\sum_{i=1}^{n}x_{i}[/math]. на сгенерированном массиве нормально распределенных значений:
<syntaxhighlight lang="rsplus"> > x <- rnorm(10^7L) > system.time(mean(x)) пользователь система прошло
0.020 0.000 0.022
> system.time(sum(x) / length(x)) пользователь система прошло
0.013 0.000 0.013
</syntaxhighlight>
Функция возвращает 3 значения:
- user - время CPU, которое занял пользователь;
- system - время CPU, которое заняла система;
- elapsed - реальное время, которое заняло выполнение команды.
Соответственно, в выводе функции нас интересует значение Шаблон:Inline-code, который показывает время выполнения функции (выражения) в секундах. Как мы видим, внешне более сложное выражение Шаблон:Inline-code выполняется быстрее стандартной функции Шаблон:Inline-code.
К сожалению, подобный способ достаточно ненадежен, так как для оценки времени выполнения выражения функция Шаблон:Inline-code обращается к системным значениям времени. Следовательно, если во время выполнения кода параллельно производятся и другие операции на компьютере (а такое случается практически в ста процентах случаев), то возможно увеличение времени выполнения R-кода. Некоторую вариативность результатов можно увидеть, даже если выполнить функцию Шаблон:Inline-code несколько раз подряд. Подобной неточности оценки можно избежать путём многократного повторения выполняемых выражений и вычислением среднего времени, что позволит сгладить часть вариаций.
Проиллюстрируем вышесказанное на примере:
<syntaxhighlight lang="rsplus"> > replicate(10, system.time(mean(x))"elapsed")
[1] 0.023 0.021 0.021 0.021 0.022 0.021 0.022 0.021 0.021 0.021
</syntaxhighlight>
В этом примере с помощью функции Шаблон:Inline-code мы повторили выражение Шаблон:Inline-code 10 раз, отфильтровав вывод функции Шаблон:Inline-code так, чтобы нам выводилось только время выполнения команды, дописав Шаблон:Inline-code. Как мы видим, время выполнения при повторном выполнении выражения может отличаться.
Базовый пакет позволяет реализовать процедуру многократного повторения выражения функции как минимум двумя способами. Первый - функция Шаблон:Inline-code. Приведенное выше сопоставление времени выполнения двух выражений при использовании функции Шаблон:Inline-code будет выглядеть следующим образом:
<syntaxhighlight lang="rsplus"> > system.time(replicate(100, mean(x))) пользователь система прошло
2.166 0.000 2.162
> system.time(replicate(100, sum(x) / length(x))) пользователь система прошло
1.167 0.003 1.186
</syntaxhighlight>
Тот же самый эффект можно получить и с помощью обычного цикла Шаблон:Inline-code:
<syntaxhighlight lang="rsplus"> > system.time(for (i in seq_len(100)) mean(x)) пользователь система прошло
2.110 0.003 2.126
> system.time(for (i in seq_len(100)) sum(x) / length(x)) пользователь система прошло
1.070 0.000 1.085
</syntaxhighlight>
Можно также использовать описательные статистики в сочетании с множественными повторениями:
<syntaxhighlight lang="rsplus"> > median(replicate(100, system.time(mean(x))"elapsed")) [1] 0.021 </syntaxhighlight>
В примере выше мы взяли только значения Шаблон:Inline-code и рассчитали медиану [1].
Вместо подобных решений можно использовать специальные пакеты, предназначенные для измерения производительности кода, в частности, пакеты Шаблон:Inline-code и Шаблон:Inline-code. Основной принцип работы этих пакетов заключается в многократном выполнении выражений и расчёта ряда интегральных показателей, в частности, суммы, среднего значения или медианы времени выполнения всех попыток.
Пакет rbenchmark
Основа пакета Шаблон:Inline-code - функция Шаблон:Inline-code. Данная функция работает следующим образом: указанные в качестве аргументов выражения выполняются заданное количество раз (по умолчанию 100) и вычисляется время, затраченное на выполнение всех попыток. В качестве аргументов функции Шаблон:Inline-code необходимо передать выражения или функции, а также количество повторений, передаваемых аргументом replications[2].
Для примера возьмём несколько способов расчёта среднего арифметического для сгенерированного массива данных.
Использованные нами способы - функции векторизованных вычислений (Шаблон:Inline-code, Шаблон:Inline-code), стандартный цикл и специальная функция вычисления средних по столбцам Шаблон:Inline-code. Представим эти способы в виде самостоятельных функций для удобства их вызова при работе с Шаблон:Inline-code:
<syntaxhighlight lang="rsplus"> > x <- replicate(10, rnorm(10^6L)) </syntaxhighlight>
<syntaxhighlight lang="rsplus"> CMApply <- function(x) {
apply(x, 2, mean)
}
CMVapply <- function(x) {
vapply(seq_len(ncol(x)), function(i) mean(x[, i]), FUN.VALUE = numeric(1))
}
CMLoop <- function(x) {
n.vars <- ncol(x) res <- double(n.vars) for (i in seq_len(n.vars)) res[i] <- mean(x[, i]) return(res)
}
CMLoopVec <- function(x) {
n.vars <- ncol(x) n <- nrow(x) res <- double(n.vars) for (i in seq_len(n.vars)) res[i] <- sum(x[, i]) / n return(res)
} </syntaxhighlight>
Убедимся, что функции возвращают одинаковый результат. Сделать это можно с помощью функций Шаблон:Inline-code или Шаблон:Inline-code:
<syntaxhighlight lang="rsplus"> > identical(CMApply(x), CMVapply(x), CMLoop(x), CMLoopVec(x), ColMeans(x)) </syntaxhighlight>
Теперь, подключив пакет rbenchmark, мы можем сравнить время работы каждого из выбранных нами способов вычисления средних по столбцам:
<syntaxhighlight lang="rsplus"> > library(rbenchmark) > benchmark(CMApply(x), CMVapply(x), CMLoop(x),
CMLoopVec(x), ColMeans(x), replications = 100)
</syntaxhighlight>
Наиболее важны для нас в выводе функции Шаблон:Inline-code столбцы elapsed и relative. Столбец elapsed показывает время, затраченное на выполнение интересующей нас функции. Как видим из примера, самыми медленными оказались функции Шаблон:Inline-code и Шаблон:Inline-code, а самой быстрой Шаблон:Inline-code, причём превосходит остальные по скорости выполнения как минимум в 7 раз.
Показатель relative дает информацию о разнице во времени относительно самого быстрого выражения (в нашем случае это Шаблон:Inline-code), т.е. время самого быстрого выражения берётся за единицу, и рассчитывается относительное время для остальных выражений.
Для более удобного просмотра можно отфильтровать вывод функции Шаблон:Inline-code с помощью аргумента columns. Также может быть полезен аргумент order, позволяющий отсортировать вывод по любому из столбцов. Для примера зададим набор показателей, которые мы хотим включить в таблицу (в данном случае это «test», «replications», «elapsed», «relative»), и отсортируем выдачу по столбцу «elapsed» по возрастанию значений:
<syntaxhighlight lang="rsplus"> > benchmark(CMApply(x), CMVapply(x), CMLoop(x), CMLoopVec(x), ColMeans(x),
replications = 100, order = "relative", columns = c("test", "replications", "elapsed", "relative"))
</syntaxhighlight>
Таким образом, сравнив несколько альтернатив решения нашей задачи, мы можем сделать обоснованный выбор в пользу наиболее эффективного варианта.
Чтобы не указывать нужные столбцы каждый раз, когда используется функция Шаблон:Inline-code, можно закрепить заданный формат выдачи результатов (далее используется именно такой формат вывода, с сортировкой по столбцу «relative»). Для этого следует воспользоваться функцией Шаблон:Inline-code:
<syntaxhighlight lang="rsplus"> > formals(benchmark)$columns <- c("test", "replications", "elapsed", "relative") > formals(benchmark)$order <- "relative" </syntaxhighlight>
Пакет microbenchmark
Примечания
- ↑ Медиана является более устойчивой мерой центральной тенденции при асимметрии распределения, что, как правило, характерно для измерения времени.
- ↑ Анализа функции Шаблон:Inline-code показал, что, данная функция использует Шаблон:Inline-code и Шаблон:Inline-code, рассмотренные в предыдущем разделе.