Изменения

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

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

279 байтов убрано, 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 }} ==
Функция {{Inline-<code|>microbenchmark()|lang="rsplus"}} </code> одноименного пакета работает сходным с функцией {{Inline-<code|>benchmark()|lang="rsplus"}} </code> образом, но предоставляет более гибкие средства по управлению процессом выполнения выражений<ref>Но в отличии от функции benchmark() использует собственную реализацию измерения времени выполнения и организацию повторных испытаний.</ref>. Также с помощью функции Особенностями реализованных в пакете {{Inliner-codepackage|microbenchmark()|lang="rsplus"}} можно получить исходную информацию о времени выполнения каждой попытки, что даёт достаточно широкие возможности по обработке и анализу полученных результатов. В таблице ниже представлено время выполнения пяти функций вычисления среднего значения из предыдущего примера, полученное с помощью функции {{Inline-code|microbenchmark()|lang="rsplus"}}являются:
<syntaxhighlight lang="rsplus">* Возможность измерения времени выполнения выражения вплоть до наносекунд;> library(microbenchmark)> 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* Возможность проведения предварительных испытаний до начала процесса измерений.796 7.828 7.87 7.943 8.063 100 colMeansLoopVec(x) 67.495 68.524 75.11 81.684 101.999 100 colMeansVapply(x) 75.791 76.648 85.47 90.325 94.803 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>
Все результаты представлены в виде описательных статистик, рассчитанных из Также с помощью функции <code>microbenchmark()</code> можно получить исходную информацию о времени выполнения каждой попытки. Наиболее информативный столбец - это столбец median, который показывает медиану времени выполнения выражения для всех попытокчто даёт достаточно широкие возможности по обработке и анализу полученных результатов.
Вся полученная информация о попытках применения В таблице ниже представлено время выполнения пяти функций вычисления средних записана в отдельную переменную {{Inline-code|res|lang="rsplus"}}. С среднего значения из предыдущего примера, полученное с помощью функции {{Inline-<code|str>microbenchmark()|lang="rsplus"}} можно увидеть структуру переменной</code>:
{{r-code|code=<syntaxhighlight langnowiki>res <- microbenchmark(colMeansApply(x), colMeansVapply(x), colMeansLoop(x), colMeansLoopVec(x), colMeans(x), times = 100)print(res, unit ="rsplusms", order = "median")#>Unit: milliseconds#> str expr min lq median uq max neval#> colMeans(resx) 7.805 7.824 7.841 7.865 8.295 100Classes ‘microbenchmark’ and 'data#> colMeansLoopVec(x) 65.frame': 500 obs429 67. of 680 2 variables:77.083 $ expr: Factor w/ 5 levels "colMeansApply79.558 95.667 100#> colMeansVapply(x)", 73.158 74.: 4 2 1 1 1 3 3 5 5 1 701 83.481 87.724 102.963 100#> colMeansLoop(x) $ time: num 73.163 175.02e+08 8355 85.55e+07 1780 87.04e+08 1986 110.389 100#> colMeansApply(x) 103.10e+08 1357 138.29e+08 125 145.323 148.876 180.600 100</syntaxhighlightnowiki>}}
Переменная {{Inline-code|res|lang="rsplus"}}, как можно увидеть Все результаты представлены в выводе функции {{Inline-code|str()|lang="rsplus"}}виде описательных статистик, представляет собой список (list) и включает в себя две переменные: {{Inline-code|expr|lang="rsplus"}} (выражение) и {{Inline-code|time|lang="rsplus"}} (время рассчитанных из времени выполнения)каждой попытки. На основе этой информации и рассчитываются описательные статистики, приведённые в примере применения функции {{InlineНаиболее информативный столбец -code|microbenchmark()|lang="rsplus"}}. Наличие исходных данных о каждой попытке позволяет самостоятельно выбирать, рассчитывать и сравнивать предпочтитаемые показатели. Напримерэто столбец median, расчет медианного времени выполнения попытки и общего который показывает медиану времени выполнения выражения для всех попыток для каждого выражения выглядит следующим образом: .
Вся полученная информация о попытках применения функций вычисления средних записана в отдельную переменную <syntaxhighlight lang="rsplus"code>res</code> aggregate(time ~ expr, data = res, function(x) median(x) * 10^-6L) expr time1 colMeansApply(x) 132.242 colMeansVapply(x) 85.473 colMeansLoop(x) 85.654 colMeansLoopVec(x) 75.115 colMeans(x) 7.87С помощью функции <code> aggregatestr(time ~ expr, data = res, function(x) sum(x) * 10^-6L) expr time1 colMeansApply(x) 13015.82 colMeansVapply(x) 8400.43 colMeansLoop(x) 8447.84 colMeansLoopVec(x) 7576.85 colMeans(x) 788.8</syntaxhighlightcode>можно увидеть структуру переменной:
Умножение на <math>10^{{r-6}code|code=</mathnowiki> --- это перевод в миллисекундыstr(res)#> Classes 'microbenchmark' and 'data. Чтобы получить секунды, нужно, соответственноframe': 500 obs. of 2 variables:#> $ expr: Factor w/ 5 levels "colMeansApply(x)", умножить на <math..: 3 2 4 2 2 2 3 5 4 1 ...#>10^{-9} $ time: num 86781202 81161181 65429189 74434388 74424478 ...</mathnowiki>.}}
Помимо настройки формата вывода выбора показателейПеременная <code>res</code>, наличие информации о времени выполнения выражения как можно увидеть в каждой попытке позволяет визуализировать результаты оценки времени выводе функции <code>str()</code>, представляет собой список (list) и включает в себя две переменные: <code>expr</code> (выражение) и <code>time</code> (время выполнения выражения). НапримерНа основе этой информации и рассчитываются описательные статистики, с помощью приведённые в примере применения функции {{Inline-<code|autoplot>microbenchmark()|lang="rsplus"}} из пакета {{Inline-</code|ggplot2|lang="rsplus"}}>. Наличие исходных данных о каждой попытке позволяет самостоятельно выбирать, можно получить следующий графикрассчитывать и сравнивать предпочтитаемые показатели. Например, расчет медианного времени выполнения попытки и общего времени выполнения всех попыток для каждого выражения выглядит следующим образом:
{{r-code|code=<syntaxhighlight langnowiki>aggregate(time ~ expr, data ="rsplus"res, function(x) median(x) * 10^-6L)#> expr time#> library1 colMeansApply(ggplot2x)145.323#> autoplot2 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</syntaxhighlightnowiki>}}
[[Файл:MicrobenchmarkУмножение на <math>10^{-autoplot6}</math> -colMeans-- это перевод в миллисекунды. Чтобы получить секунды, нужно, соответственно, умножить на <math>10^{-9}</math>.png|600px|центр]]
Ещё один довольно интересный способ графического представления результатов измерения скорости Помимо настройки формата вывода, выбора показателей, наличие информации о времени выполнения кода выражения в каждой попытке позволяет визуализировать результаты оценки времени выполнения выражения. Например, с помощью функции {{Inline-<code|qplot>autoplot()</code> из пакета {{r-package|lang="rsplus"ggplot2}} представлен ниже, можно получить следующий график:
{{r-code|code=<syntaxhighlight lang="rsplus"nowiki>> qplotautoplot(y = time, data = res, colour = expr)</syntaxhighlightnowiki>}}
[[Файл:Microbenchmark-dotplotautoplot-colMeans.pngsvg|600px|центр]]
Так же можно, если возникнет необходимость, оценить статистическую значимость различий во времени Ещё один довольно интересный способ графического представления результатов измерения скорости выполнения выражений. Благодаря тому, что в переменной {{Inline-кода с помощью функции <code|res|lang="rsplus"}} хранятся данные о времени выполнения каждой попытки из заданного числа, становится возможным использование статистических критериев. Выбор критерия - на усмотрение аналитика, в примере >qplot()</code> представлен ниже использовался параметрический критерий сравнения групп t-Стьюдента с поправкой уровня статистической значимости Холма для множественных сравнений:
<syntaxhighlight lang{{r-code|code="rsplus"<nowiki>> pairwise.t.testqplot(res$y = time, data = res$, colour = expr)</nowiki>}}
Pairwise comparisons using t tests with pooled SD [[Файл:Microbenchmark-dotplot-colMeans.svg|600px|центр]]
dataТак же можно, если возникнет необходимость, оценить статистическую значимость различий во времени выполнения выражений. Благодаря тому, что в переменной <code>res</code> хранятся данные о времени выполнения каждой попытки из заданного числа, становится возможным использование статистических критериев. Выбор критерия - на усмотрение аналитика, в примере ниже использовался параметрический критерий сравнения групп t-Стьюдента с поправкой уровня статистической значимости Холма для множественных сравнений: res$time and res$expr
{{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.62 31 - - #> colMeansLoopVec(x) <2e-16 2.6e-09 <2e6.9e-16 <2e-16 12 - #> colMeans(x) <2e-16 <2e-16 <2e-16 <2e-16 #> #> P value adjustment method: holm</nowiki>}}
P value adjustment method: holm </syntaxhighlight> Из вышеприведённого вывода видно, что скорость выполнения всех функций статистически значимо разлиается у всех функций, за исключением пары {{Inline-<code|>colMeansLoop(x)|lang="rsplus"}} - {{Inline</code> -<code|>colMeansVapply(x)|lang="rsplus"}} </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]]
[[Категория:Оптимизация кода]]

Навигация