R:Измерение времени выполнения выражений — различия между версиями

Материал Psylab.info - энциклопедии психодиагностики
Перейти к: навигация, поиск
м (Пакет rbenchmark)
м
Строка 23: Строка 23:
 
> x <- rnorm(10^7L)
 
> x <- rnorm(10^7L)
 
> system.time(mean(x))
 
> system.time(mean(x))
пользователь      система      прошло
+
пользователь      система      прошло  
       0.020        0.000        0.022
+
       0.020        0.000        0.023
 
> system.time(sum(x) / length(x))
 
> system.time(sum(x) / length(x))
пользователь      система      прошло
+
пользователь      система      прошло  
       0.013        0.000        0.013
+
       0.013        0.000        0.012
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Строка 43: Строка 43:
 
<syntaxhighlight lang="rsplus">
 
<syntaxhighlight lang="rsplus">
 
> replicate(10, system.time(mean(x))[["elapsed"]])
 
> 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
+
  [1] 0.024 0.020 0.019 0.018 0.017 0.016 0.015 0.015 0.015 0.015
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Строка 53: Строка 53:
 
> system.time(replicate(100, mean(x)))
 
> system.time(replicate(100, mean(x)))
 
пользователь      система      прошло  
 
пользователь      система      прошло  
       2.166       0.000        2.162
+
       1.580       0.000        1.586
 
> system.time(replicate(100, sum(x) / length(x)))
 
> system.time(replicate(100, sum(x) / length(x)))
 
пользователь      система      прошло  
 
пользователь      система      прошло  
       1.167       0.003       1.186
+
       0.817       0.016       0.835
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Строка 64: Строка 64:
 
> system.time(for (i in seq_len(100)) mean(x))
 
> system.time(for (i in seq_len(100)) mean(x))
 
пользователь      система      прошло  
 
пользователь      система      прошло  
       2.110       0.003       2.126
+
       1.583       0.000       1.590
 
> system.time(for (i in seq_len(100)) sum(x) / length(x))
 
> system.time(for (i in seq_len(100)) sum(x) / length(x))
 
пользователь      система      прошло  
 
пользователь      система      прошло  
       1.070       0.000        1.085
+
       0.797       0.000        0.800
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Строка 74: Строка 74:
 
<syntaxhighlight lang="rsplus">
 
<syntaxhighlight lang="rsplus">
 
> median(replicate(100, system.time(mean(x))[["elapsed"]]))
 
> median(replicate(100, system.time(mean(x))[["elapsed"]]))
[1] 0.021
+
[1] 0.0155
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Строка 94: Строка 94:
  
 
<syntaxhighlight lang="rsplus">
 
<syntaxhighlight lang="rsplus">
CMApply <- function(x) {
+
colMeanApply <- function(x) {
 
   apply(x, 2, mean)
 
   apply(x, 2, mean)
 
}
 
}
  
CMVapply <- function(x) {
+
colMeansVapply <- function(x) {
 
   vapply(seq_len(ncol(x)), function(i) mean(x[, i]), FUN.VALUE = numeric(1))
 
   vapply(seq_len(ncol(x)), function(i) mean(x[, i]), FUN.VALUE = numeric(1))
 
}
 
}
  
CMLoop <- function(x) {
+
colMeansLoop <- function(x) {
 
   n.vars <- ncol(x)
 
   n.vars <- ncol(x)
 
   res <- double(n.vars)
 
   res <- double(n.vars)
Строка 110: Строка 110:
 
}
 
}
  
CMLoopVec <- function(x) {
+
colMeansLoopVec <- function(x) {
 
   n.vars <- ncol(x)
 
   n.vars <- ncol(x)
 
   n <- nrow(x)
 
   n <- nrow(x)
Строка 123: Строка 123:
  
 
<syntaxhighlight lang="rsplus">
 
<syntaxhighlight lang="rsplus">
> identical(CMApply(x), CMVapply(x), CMLoop(x), CMLoopVec(x), ColMeans(x))
+
> identical(colMeanApply(x), colMeansVapply(x), colMeansLoop(x), colMeansLoopVec(x), colMeans(x))
 +
[1] TRUE
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Строка 130: Строка 131:
 
<syntaxhighlight lang="rsplus">
 
<syntaxhighlight lang="rsplus">
 
> library(rbenchmark)
 
> library(rbenchmark)
> benchmark(CMApply(x), CMVapply(x), CMLoop(x),
+
> benchmark(colMeanApply(x), colMeansVapply(x), colMeansLoop(x),
            CMLoopVec(x), ColMeans(x), replications = 100)
+
+          colMeansLoopVec(x), colMeans(x), replications = 100)
 +
          test replications elapsed relative user.self sys.self user.child sys.child
 +
1    colMeanApply(x)          100  13.206  16.802    11.893    1.260          0        0
 +
4 colMeansLoopVec(x)          100  6.931    8.818    6.793    0.113          0        0
 +
3    colMeansLoop(x)          100  7.729    9.833    7.583    0.113          0        0
 +
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>
 
</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 раз.
+
Наиболее важны для нас в выводе функции {{Inline-code|benchmark()|lang="rsplus"}} столбцы elapsed и relative. Столбец elapsed показывает время, затраченное на выполнение интересующей нас функции. Как видим из примера, самыми медленными оказались функции {{Inline-code|colMeanApply()|lang="rsplus"}} и {{Inline-code|colMeansLoop()|lang="rsplus"}}, а самой быстрой {{Inline-code|colMeans()|lang="rsplus"}}, причём превосходит остальные по скорости выполнения как минимум в 7 раз.
  
 
Показатель relative дает информацию о разнице во времени относительно самого быстрого выражения (в нашем случае это {{Inline-code|ColMeans()|lang="rsplus"}}), т.е. время самого быстрого выражения берётся за единицу, и рассчитывается относительное время для остальных выражений.
 
Показатель relative дает информацию о разнице во времени относительно самого быстрого выражения (в нашем случае это {{Inline-code|ColMeans()|lang="rsplus"}}), т.е. время самого быстрого выражения берётся за единицу, и рассчитывается относительное время для остальных выражений.
Строка 141: Строка 148:
  
 
<syntaxhighlight lang="rsplus">
 
<syntaxhighlight lang="rsplus">
> benchmark(CMApply(x), CMVapply(x), CMLoop(x), CMLoopVec(x), ColMeans(x),
+
> benchmark(colMeanApply(x), colMeansVapply(x), colMeansLoop(x), colMeansLoopVec(x), colMeans(x),
            replications = 100, order = "relative",
+
+          replications = 100, order = "relative",
            columns = c("test", "replications", "elapsed", "relative"))
+
+          columns = c("test", "replications", "elapsed", "relative"))
 +
5        colMeans(x)          100  0.782    1.000
 +
4 colMeansLoopVec(x)          100  6.890    8.811
 +
3    colMeansLoop(x)          100  7.684    9.826
 +
2  colMeansVapply(x)          100  7.716    9.867
 +
1    colMeanApply(x)          100  13.142  16.806
 
</syntaxhighlight>
 
</syntaxhighlight>
  

Версия 13:02, 23 января 2014

Для измерения времени выполнения выражений используют следующие инструменты:

  • Функция Шаблон: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.023

> system.time(sum(x) / length(x)) пользователь система прошло

      0.013        0.000        0.012

</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.024 0.020 0.019 0.018 0.017 0.016 0.015 0.015 0.015 0.015

</syntaxhighlight>

В этом примере с помощью функции Шаблон:Inline-code мы повторили выражение Шаблон:Inline-code 10 раз, отфильтровав вывод функции Шаблон:Inline-code так, чтобы нам выводилось только время выполнения команды, дописав Шаблон:Inline-code. Как мы видим, время выполнения при повторном выполнении выражения может отличаться.

Базовый пакет позволяет реализовать процедуру многократного повторения выражения функции как минимум двумя способами. Первый - функция Шаблон:Inline-code. Приведенное выше сопоставление времени выполнения двух выражений при использовании функции Шаблон:Inline-code будет выглядеть следующим образом:

<syntaxhighlight lang="rsplus"> > system.time(replicate(100, mean(x))) пользователь система прошло

      1.580        0.000        1.586

> system.time(replicate(100, sum(x) / length(x))) пользователь система прошло

      0.817        0.016        0.835

</syntaxhighlight>

Тот же самый эффект можно получить и с помощью обычного цикла Шаблон:Inline-code:

<syntaxhighlight lang="rsplus"> > system.time(for (i in seq_len(100)) mean(x)) пользователь система прошло

      1.583        0.000        1.590

> system.time(for (i in seq_len(100)) sum(x) / length(x)) пользователь система прошло

      0.797        0.000        0.800

</syntaxhighlight>

Можно также использовать описательные статистики в сочетании с множественными повторениями:

<syntaxhighlight lang="rsplus"> > median(replicate(100, system.time(mean(x))"elapsed")) [1] 0.0155 </syntaxhighlight>

В примере выше мы взяли только значения Шаблон:Inline-code и рассчитали медиану [1].

Вместо подобных решений можно использовать специальные пакеты, предназначенные для измерения производительности кода, в частности, пакеты Шаблон:Inline-code и Шаблон:Inline-code. Основной принцип работы этих пакетов заключается в многократном выполнении выражений и расчёта ряда интегральных показателей, в частности, суммы, среднего значения или медианы времени выполнения всех попыток.

Пакет rbenchmark

Основа пакета Шаблон:Inline-code - функция Шаблон:Inline-code. Данная функция работает следующим образом: указанные в качестве аргументов выражения выполняются заданное количество раз (по умолчанию 100) и вычисляется время, затраченное на выполнение всех попыток. В качестве аргументов функции Шаблон:Inline-code необходимо передать выражения или функции, а также количество повторений, передаваемых аргументом replications[2].

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

<syntaxhighlight lang="rsplus"> > x <- replicate(10, rnorm(10^6L)) </syntaxhighlight>

Использованные нами способы - функции векторизованных вычислений (Шаблон:Inline-code, Шаблон:Inline-code), стандартный цикл и специальная функция вычисления средних по столбцам Шаблон:Inline-code. Представим эти способы в виде самостоятельных функций для удобства их вызова при работе с Шаблон:Inline-code:

<syntaxhighlight lang="rsplus"> colMeanApply <- function(x) {

 apply(x, 2, mean)

}

colMeansVapply <- function(x) {

 vapply(seq_len(ncol(x)), function(i) mean(x[, i]), FUN.VALUE = numeric(1))

}

colMeansLoop <- function(x) {

 n.vars <- ncol(x)
 res <- double(n.vars)
 for (i in seq_len(n.vars))
   res[i] <- mean(x[, i])
 return(res)

}

colMeansLoopVec <- 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(colMeanApply(x), colMeansVapply(x), colMeansLoop(x), colMeansLoopVec(x), colMeans(x)) [1] TRUE </syntaxhighlight>

Теперь, подключив пакет rbenchmark, мы можем сравнить время работы каждого из выбранных нами способов вычисления средних по столбцам:

<syntaxhighlight lang="rsplus"> > library(rbenchmark) > benchmark(colMeanApply(x), colMeansVapply(x), colMeansLoop(x), + colMeansLoopVec(x), colMeans(x), replications = 100)

         test replications elapsed relative user.self sys.self user.child sys.child

1 colMeanApply(x) 100 13.206 16.802 11.893 1.260 0 0 4 colMeansLoopVec(x) 100 6.931 8.818 6.793 0.113 0 0 3 colMeansLoop(x) 100 7.729 9.833 7.583 0.113 0 0 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>

Наиболее важны для нас в выводе функции Шаблон: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(colMeanApply(x), colMeansVapply(x), colMeansLoop(x), colMeansLoopVec(x), colMeans(x), + replications = 100, order = "relative", + columns = c("test", "replications", "elapsed", "relative")) 5 colMeans(x) 100 0.782 1.000 4 colMeansLoopVec(x) 100 6.890 8.811 3 colMeansLoop(x) 100 7.684 9.826 2 colMeansVapply(x) 100 7.716 9.867 1 colMeanApply(x) 100 13.142 16.806 </syntaxhighlight>

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

Чтобы не указывать нужные столбцы каждый раз, когда используется функция Шаблон:Inline-code, можно закрепить заданный формат выдачи результатов (далее используется именно такой формат вывода, с сортировкой по столбцу «relative»). Для этого следует воспользоваться функцией Шаблон:Inline-code:

<syntaxhighlight lang="rsplus"> > formals(benchmark)$columns <- c("test", "replications", "elapsed", "relative") > formals(benchmark)$order <- "relative" </syntaxhighlight>

Пакет microbenchmark

Примечания

  1. Медиана является более устойчивой мерой центральной тенденции при асимметрии распределения, что, как правило, характерно для измерения времени.
  2. Анализа функции Шаблон:Inline-code показал, что, данная функция использует Шаблон:Inline-code и Шаблон:Inline-code, рассмотренные в предыдущем разделе.