Изменения

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

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

7921 байт добавлено, 14:45, 28 мая 2014
м
Загрузка 2693-time-measure.wiki
<!-- R:Измерение времени выполнения выражений -->   {{CC-BY-4.0|author=автором Артём Клевцов}} {{Pkg-req-notice}}
Для измерения времени выполнения выражений используют следующие инструменты:
* Функция {{Inline-<code|>system.time()|lang=rsplus}} </code> из базового пакета;
* Cпециализированные функции (benchmarks, бенчмарки);
* Функция профилирования времени выполнения кода {{Inline-<code|>Rprof()|lang=rsplus}}</code>.
== Функция <code>system.time() </code> ==
Самый простой инструмент для измерения времени выполнения кода - функция {{Inline-<code|>system.time()|lang="rsplus"}} </code> из пакета {{Inliner-codepackage|base|langcore="rsplus"true}}. В качестве аргумента функция {{Inline-<code|>system.time()|lang="rsplus"}} </code> принимает выражения и возвращает время выполнения данного выражения. Измерим время выполнения функции {{Inline-<code|>Sys.sleep()|lang="rsplus"}}</code>, которая останавливает выполнение кода на заданный интервал времени (в секундах):
{{r-code|code=<syntaxhighlight lang="rsplus">nowiki> system.time(Sys.sleep(1))пользователь система прошло#> user system elapsed #> 0.003 000 0.004 000 1.000001</syntaxhighlightnowiki>}}
Как видим, на выполнение данной операции заняло ровно одну секунду.
Приведём ещё один пример. Сравним время вычисления встроенной в R функции {{Inline-<code|>mean()|lang="rsplus"}} </code> и среднего, вычисленного по формуле <math>\frac{1}{n}\sum_{i=1}^{n}x_{i}</math>. на сгенерированном массиве нормально распределенных значений:
{{r-code|code=<syntaxhighlight lang="rsplus">nowiki> x <- rnorm(10^7L)> system.time(mean(x))пользователь система прошло #> user system elapsed #> 0.020 013 0.000 003 0.023016> system.time(sum(x) / length(x))пользователь система прошло #> user system elapsed #> 0.013 010 0.000 0.012008</syntaxhighlightnowiki>}}
Функция возвращает 3 значения:
* <code>user </code> - время CPU, которое занял пользователь;* <code>system </code> - время CPU, которое заняла система;* <code>elapsed </code> - реальное время, которое заняло выполнение команды.
Соответственно, в выводе функции нас интересует значение {{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> несколько раз подряд. Подобной неточности оценки можно избежать путём многократного повторения выполняемых выражений и вычислением среднего времени, что позволит сгладить часть вариаций.
Проиллюстрируем вышесказанное на примере:
{{r-code|code=<syntaxhighlight lang="rsplus">nowiki> replicate(10, system.time(mean(x))[["elapsed"]])#> [1] 0.024 016 0.020 016 0.019 016 0.018 016 0.016 0.017 0.016 0.015 017 0.015 017 0.015 0.015017</syntaxhighlightnowiki>}}
В этом примере с помощью функции {{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> будет выглядеть следующим образом:
{{r-code|code=<syntaxhighlight lang="rsplus">nowiki> system.time(replicate(100, mean(x)))пользователь система прошло #> user system elapsed #> 1.580 586 0.000 027 1.586621> system.time(replicate(100, sum(x) / length(x)))пользователь система прошло #> user system elapsed #> 0.817 806 0.016 020 0.835832</syntaxhighlightnowiki>}}
Тот же самый эффект можно получить и с помощью обычного цикла {{Inline-<code|>for()|lang="rsplus"}}</code>:
{{r-code|code=<syntaxhighlight lang="rsplus">nowiki> system.time(for (i in seq_len(100)) mean(x))пользователь система прошло #> user system elapsed #> 1.583 563 0.000 050 1.590620> system.time(for (i in seq_len(100)) sum(x) / length(x))пользователь система прошло #> user system elapsed #> 0.797 806 0.000 020 0.800830</syntaxhighlightnowiki>}}
Можно также использовать описательные статистики в сочетании с множественными повторениями:
{{r-code|code=<syntaxhighlight lang="rsplus">nowiki> median(replicate(100, system.time(mean(x))[["elapsed"]]))#> [1] 0.0155016</syntaxhighlightnowiki>}}
В примере выше мы взяли только значения {{Inline-<code|>elapsed|lang="rsplus"}} </code> и рассчитали медиану <ref>Медиана является более устойчивой мерой центральной тенденции при асимметрии распределения, что, как правило, характерно для измерения времени.</ref>.
Вместо подобных решений можно использовать специальные пакеты, предназначенные для измерения производительности кода, в частности, пакеты {{Inliner-codepackage|rbenchmark|lang="rsplus"}} и {{Inliner-codepackage|microbenchmark|lang="rsplus"}}. Основной принцип работы этих пакетов заключается в многократном выполнении выражений и расчёта ряда интегральных показателей, в частности, суммы, среднего значения или медианы времени выполнения всех попыток.
== Пакет {{r-package|rbenchmark }} ==
Основа пакета {{Inliner-codepackage|rbenchmark|lang="rsplus"}} - функция {{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>.
Для примера возьмём несколько способов расчёта среднего арифметического для сгенерированного массива данных.
{{r-code|code=<syntaxhighlight lang="rsplus">nowiki> x <- replicate(10, rnorm(10^6L))</syntaxhighlightnowiki>}}
Использованные нами способы - функции векторизованных вычислений ({{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{{r-code|code="rsplus"<nowiki>colMeansApply <- function(x) {
apply(x, 2, mean)
}
return(res)
}
</syntaxhighlightnowiki>}}
Убедимся, что функции возвращают одинаковый результат. Сделать это можно с помощью функций {{Inline-<code|>identical()|lang="rsplus"}} </code> или {{Inline-<code|>all.equal()|lang="rsplus"}}</code>:
{{r-code|code=<syntaxhighlight lang="rsplus">nowiki> identical(colMeansApply(x), colMeansVapply(x), colMeansLoop(x), colMeansLoopVec(x), colMeans(x))#> [1] TRUE</syntaxhighlightnowiki>}}
Теперь, подключив пакет {{r-package|rbenchmark}}, мы можем сравнить время работы каждого из выбранных нами способов вычисления средних по столбцам:
{{r-code|code=<syntaxhighlight lang="rsplus">> library(rbenchmark)nowiki> benchmark(colMeansApply(x), colMeansVapply(x), colMeansLoop(x),+ colMeansLoopVec(x), colMeans(x), replications = 100) #> test replications elapsed relative user.self sys.self user.child sys.child#> 1 colMeansApply(x) 100 1314.206 974 1618.802 328 1113.893 380 1.260 540 0 0#> 4 colMeansLoopVec(x) 100 67.931 088 8.818 676 6.793 0.113 270 0 0#> 3 colMeansLoop(x) 100 78.729 004 9.833 797 7.583 630 0.113 340 0 0#> 2 colMeansVapply(x) 100 7.724 851 9.827 610 7.630 407 0.067 417 0 0#> 5 colMeans(x) 100 0.786 817 1.000 0.784 787 0.000 026 0 0</syntaxhighlightnowiki>}}
Наиболее важны для нас в выводе функции {{Inline-<code|>benchmark()|lang="rsplus"}} </code> столбцы <code>elapsed </code> и <code>relative</code>. Столбец <code>elapsed </code> показывает времяв секундах, затраченное на выполнение интересующей нас функции. Как видим из примера, самыми медленными оказались функции {{Inline-<code|>colMeansApply()|lang="rsplus"}} </code> и {{Inline-<code|>colMeansLoop()|lang="rsplus"}}</code>, а самой быстрой {{Inline-<code|>colMeans()|lang="rsplus"}}</code>, причём превосходит остальные по скорости выполнения как минимум в 7 раз.
Показатель <code>relative </code> дает информацию о разнице во времени относительно самого быстрого выражения (в нашем случае это {{Inline-<code|>ColMeans()|lang="rsplus"}}</code>), т.е. время самого быстрого выражения берётся за единицу, и рассчитывается относительное время для остальных выражений.
Для более удобного просмотра можно отфильтровать вывод функции {{Inline-<code|>benchmark()|lang="rsplus"}} </code> с помощью аргумента <code>columns</code>. Также может быть полезен аргумент <code>order</code>, позволяющий отсортировать вывод по любому из столбцов. Для примера зададим набор показателей, которые мы хотим включить в таблицу (в данном случае это «test», «replications», «elapsed», «relative»), и отсортируем выдачу по столбцу «elapsed» по возрастанию значений:
{{r-code|code=<syntaxhighlight lang="rsplus">nowiki> benchmark(colMeansApply(x), colMeansVapply(x), colMeansLoop(x), colMeansLoopVec(x), colMeans(x),+ replications = 100, order = "relative",+ columns = c("test", "replications", "elapsed", "relative"))#> test replications elapsed relative#> 5 colMeans(x) 100 0.782 797 1.000#> 4 colMeansLoopVec(x) 100 6.890 856 8.811602#> 3 colMeansLoop(x) 100 7.684 633 9.826577#> 2 colMeansVapply(x) 100 7.716 722 9.867689#> 1 colMeansApply(x) 100 1315.142 167 1619.806030</syntaxhighlightnowiki>}}
Таким образом, сравнив несколько альтернатив решения нашей задачи, мы можем сделать обоснованный выбор в пользу наиболее эффективного варианта.
Чтобы не указывать нужные столбцы каждый раз, когда используется функция {{Inline-<code|>benchmark()|lang="rsplus"}}</code>, можно закрепить заданный формат выдачи результатов (далее используется именно такой формат вывода, с сортировкой по столбцу «relative»). Для этого следует воспользоваться функцией {{Inline-<code|>formals()|lang="rsplus"}}</code>:
{{r-code|code=<syntaxhighlight lang="rsplus">nowiki> formals(benchmark)$columns <- c("test", "replications", "elapsed", "relative")> formals(benchmark)$order <- "relative"</syntaxhighlightnowiki>}}
== Пакет {{r-package|microbenchmark }} == Функция <code>microbenchmark()</code> одноименного пакета работает сходным с функцией <code>benchmark()</code> образом, но предоставляет более гибкие средства по управлению процессом выполнения выражений<ref>Но в отличии от функции benchmark() использует собственную реализацию измерения времени выполнения и организацию повторных испытаний.</ref>. Особенностями реализованных в пакете {{r-package|microbenchmark}} являются: * Возможность измерения времени выполнения выражения вплоть до наносекунд;* Возможность контролировать последовательность выполнения выражений: случайно или последовательно;* Возможность проведения предварительных испытаний до начала процесса измерений. Также с помощью функции <code>microbenchmark()</code> можно получить исходную информацию о времени выполнения каждой попытки, что даёт достаточно широкие возможности по обработке и анализу полученных результатов. В таблице ниже представлено время выполнения пяти функций вычисления среднего значения из предыдущего примера, полученное с помощью функции <code>microbenchmark()</code>: {{r-code|code=<nowiki>res <- microbenchmark(colMeansApply(x), colMeansVapply(x), colMeansLoop(x), colMeansLoopVec(x), colMeans(x), times = 100)print(res, unit = "ms", order = "median")#> Unit: milliseconds#> expr min lq median uq max neval#> colMeans(x) 7.805 7.824 7.841 7.865 8.295 100#> colMeansLoopVec(x) 65.429 67.680 77.083 79.558 95.667 100#> colMeansVapply(x) 73.158 74.701 83.481 87.724 102.963 100#> colMeansLoop(x) 73.163 75.355 85.780 87.986 110.389 100#> colMeansApply(x) 103.357 138.125 145.323 148.876 180.600 100</nowiki>}} Все результаты представлены в виде описательных статистик, рассчитанных из времени выполнения каждой попытки. Наиболее информативный столбец - это столбец median, который показывает медиану времени выполнения выражения для всех попыток. Вся полученная информация о попытках применения функций вычисления средних записана в отдельную переменную <code>res</code>. С помощью функции <code>str()</code> можно увидеть структуру переменной: {{r-code|code=<nowiki>str(res)#> Classes 'microbenchmark' and 'data.frame': 500 obs. of 2 variables:#> $ expr: Factor w/ 5 levels "colMeansApply(x)",..: 3 2 4 2 2 2 3 5 4 1 ...#> $ time: num 86781202 81161181 65429189 74434388 74424478 ...</nowiki>}} Переменная <code>res</code>, как можно увидеть в выводе функции <code>str()</code>, представляет собой список (list) и включает в себя две переменные: <code>expr</code> (выражение) и <code>time</code> (время выполнения). На основе этой информации и рассчитываются описательные статистики, приведённые в примере применения функции <code>microbenchmark()</code>. Наличие исходных данных о каждой попытке позволяет самостоятельно выбирать, рассчитывать и сравнивать предпочтитаемые показатели. Например, расчет медианного времени выполнения попытки и общего времени выполнения всех попыток для каждого выражения выглядит следующим образом:  {{r-code|code=<nowiki>aggregate(time ~ expr, data = res, function(x) median(x) * 10^-6L)#> expr time#> 1 colMeansApply(x) 145.323#> 2 colMeansVapply(x) 83.481#> 3 colMeansLoop(x) 85.780#> 4 colMeansLoopVec(x) 77.083#> 5 colMeans(x) 7.841aggregate(time ~ expr, data = res, function(x) sum(x) * 10^-6L)#> expr time#> 1 colMeansApply(x) 14224.3#> 2 colMeansVapply(x) 8193.4#> 3 colMeansLoop(x) 8309.6#> 4 colMeansLoopVec(x) 7483.0#> 5 colMeans(x) 785.7</nowiki>}} Умножение на <math>10^{-6}</math> --- это перевод в миллисекунды. Чтобы получить секунды, нужно, соответственно, умножить на <math>10^{-9}</math>. Помимо настройки формата вывода, выбора показателей, наличие информации о времени выполнения выражения в каждой попытке позволяет визуализировать результаты оценки времени выполнения выражения. Например, с помощью функции <code>autoplot()</code> из пакета {{r-package|ggplot2}}, можно получить следующий график: {{r-code|code=<nowiki>autoplot(res)</nowiki>}} [[Файл:Microbenchmark-autoplot-colMeans.svg|600px|центр]] Ещё один довольно интересный способ графического представления результатов измерения скорости выполнения кода с помощью функции <code>qplot()</code> представлен ниже: {{r-code|code=<nowiki>qplot(y = time, data = res, colour = expr)</nowiki>}} [[Файл:Microbenchmark-dotplot-colMeans.svg|600px|центр]] Так же можно, если возникнет необходимость, оценить статистическую значимость различий во времени выполнения выражений. Благодаря тому, что в переменной <code>res</code> хранятся данные о времени выполнения каждой попытки из заданного числа, становится возможным использование статистических критериев. Выбор критерия - на усмотрение аналитика, в примере ниже использовался параметрический критерий сравнения групп t-Стьюдента с поправкой уровня статистической значимости Холма для множественных сравнений: {{r-code|code=<nowiki>pairwise.t.test(res$time, res$expr)#> #> Pairwise comparisons using t tests with pooled SD #> #> data: res$time and res$expr #> #> colMeansApply(x) colMeansVapply(x) colMeansLoop(x) colMeansLoopVec(x)#> colMeansVapply(x) < 2e-16 - - - #> colMeansLoop(x) < 2e-16 0.31 - - #> colMeansLoopVec(x) < 2e-16 2.6e-09 6.9e-12 - #> colMeans(x) < 2e-16 < 2e-16 < 2e-16 < 2e-16 #> #> P value adjustment method: holm</nowiki>}} Из вышеприведённого вывода видно, что скорость выполнения всех функций статистически значимо разлиается у всех функций, за исключением пары <code>colMeansLoop(x)</code> - <code>colMeansVapply(x)</code> (p-уровень = 0.62).
== Примечания ==
<references />
 
== Ссылки ==
 
* Olaf Mersmann (2013). microbenchmark: Sub microsecond accurate timing functions.. R package version 1.3-0.
*: http://CRAN.R-project.org/package=microbenchmark
* Wacek Kusnierczyk (2012). rbenchmark: Benchmarking routine for R. R package version 1.0.0.
*: http://CRAN.R-project.org/package=rbenchmark
[[Категория:R]]
[[Категория:Оптимизация кода]]

Навигация