Изменения

R:Профилирование кода

3366 байтов добавлено, 14:45, 28 мая 2014
м
Загрузка 2698-code-profiling.wiki
<!-- R:Профилирование кода -->
 
 
 
{{CC-BY-4.0|author=автором Артём Клевцов}}
В качестве примера ниже представлены шаги процесса профилирования функции, которая рассчитывает первичные описательные статистики (среднее, медиану, стандартное отклонение, минимум и максимум) по сгенерированным <math>10^6</math> нормально распределённым значениям.
{{r-code|code=<nowiki>> x <- rnorm(10^6)</nowiki>}}
Объявим данную функцию:
{{r-code|code=<nowiki>desc <- function(x) {
n <- length(x)
mean <- mean(x)
return(c(n, mean, median, sd, min, max))
}
</nowiki>}}
Шаг 1. Создание временного файла для записи лога профилирования (необязательно):
{{r-code|code=<nowiki>> tmp.log <- tempfile(pattern = "profrprof-", fileext = ".log")</nowiki>}}
Посмотреть путь и имя временного файла лога можно, введя имя переменной <code>tmp.log</code>:
{{r-code|code=<nowiki>> tmp.log#> [1] "/tmp/RtmpAvhCfORtmp1kJ8SI/profrprof-23b61d76180544673946768c.log"</nowiki>}}
Шаг 2. Собственно процедура профилирования:
{{r-code|code=<nowiki># Включаем профилирование> Rprof(tmp.log)
# Выполняем код
> for (i in seq_len(100))+ invisible(desc(x))
# отключаем профилирование
> Rprof(NULL)</nowiki>}}
Шаг 3. Вывод результатов:
{{r-code|code=<nowiki>> summaryRprof(tmp.log)</nowiki>}}
Шаг 4. Удаляем временный файл:
{{r-code|code=<nowiki>> unlink(tmp.log)</nowiki>}}
Рассмотрим результаты профилирования функции <code>desc()</code>:
{{r-code|code=<nowiki>> summaryRprof(tmp.log)#> $by.self#> self.time self.pct total.time total.pct#> "sort.int" 23.54 94 5266.70 33 34.04 32 6372.0773#> "is.naCall" 0.70 1450 8.52 42 0.70 1450 8.5242#> "is.Callna" 0.44 48 98.13 08 0.44 48 98.1308#> "minany" 0.38 34 75.88 72 0.38 34 75.8872#> "anymin" 0.32 65.64 39 0.32 65.6439#> "max" 0.26 54.39 38 0.26 54.3938#> "mean.default" 0.18 06 31.73 01 0.18 06 31.7301#> "mean" 0.02 0.34 4.40 74.07#> "sys.parent" 0.02 0.34 0.02 0.34#> #> $by.total #> total.time total.pct self.time self.pct#> "descblock_exec" 4 5.82 94 100.00 0.00 0.00#> "mediancall_block" 3 5.56 94 100.00 0.00 730.86 00#> "evaluate" 5.94 100.00 0.00 0.00#> "medianevaluate_call" 5.default94 100.00 0.00 0.00#> "in_dir" 5.94 100.00 0.00 0.00#> "knit" 5.94 100.00 0.00 0.00#> "process_file" 5.94 100.00 0.00 0.00#> "process_group" 5.94 100.00 0.00 0.00#> "process_group.block" 35.56 94 100.00 0.00 730.86 00#> "withCallingHandlers" 5.94 100.00 0.00 0.00#> "meandesc" 3 5.22 92 99.66 0.80 00 0.00#> "eval" 5.92 99.66 0.00 0.00#> "sorthandle" 5.int92 99.66 0.00 0.00#> " 3withVisible" 5.04 92 6399.07 66 20.54 5200 0.7000#> "sortmedian" 3 4.04 78 6380.07 47 0.00 0.00#> "sortmedian.default" 3 4.04 78 6380.07 47 0.00 0.00#> "ismean" 4.na40 74.07 0.02 0.34#> " sort.int" 4.32 72.73 3.94 66.33#> "sort" 4.32 72.73 0.70 00 140.52 00#> "sort.default" 4.32 72.73 0.70 1400 0.5200#> ".Call" 0.44 50 98.13 42 0.44 50 98.1342#> "sd" 0.44 50 98.13 42 0.00 0.00#> "var" 0.44 50 98.13 42 0.00 0.00#> "minis.na" 0.38 48 78.88 08 0.38 48 78.8808#> "any" 0.34 5.72 0.34 5.72#> "min" 0.32 65.64 39 0.32 65.6439#> "max" 0.26 54.39 38 0.26 54.3938#> "mean.default" 0.18 06 31.73 01 0.18 06 31.7301#> "sys.parent" 0.02 0.34 0.02 0.34#> "<Anonymous>" 0.02 0.34 0.00 0.00#> "formals" 0.02 0.34 0.00 0.00#> "match.arg" 0.02 0.34 0.00 0.00#> "sink" 0.02 0.34 0.00 0.00#> "sys.function" 0.02 0.34 0.00 0.00#> #> $sample.interval#> [1] 0.02#> #> $sampling.time#> [1] 45.8294</nowiki>}}
Результатом подобных операций является 2 таблицы, отсортированные по разным основаниям: по времени выполнения функции (<code>self.time</code>) и общему времени выполнения функции (<code>total.time</code>). В столбцах представлены абсолютные и относительные (в процентах) показатели времени выполнения.
При профилировании скриптов включение опции <code>line.profiling</code> проставляет номера строк в которых производился вызов, что позволяет проанализировать производительность всего кода:
{{r-code|code=<nowiki>> Rprof(tmp.log, line.profiling = TRUE)> source("/path/scritpt.R")> Rprof(NULL)> summaryRprof(tmp.log)> unlink(tmp.log)</nowiki>}}
== Пакет {{r-package|proftools}} ==
Также может оказаться полезной функция <code>flatProfile()</code> из пакета {{r-package|proftools}}. Данная функция выводит таблицу, сходную с таблицей «by.total» возвращаемую функцией <code>summaryRprof()</code>, в одну и сортирует их по общему времени выполнения в процентах или по времени выполнения функции в процентах, если указать <code>byTotal = FALSE</code>:
{{r-code|code=<nowiki>> library(proftools)> flatProfile(readProfileData(tmp.log)) #> total.pct total.time self.pct self.timedesc #> block_exec 100.00 45.82 94 0.00 0.00median #> call_block 73100.86 00 35.56 94 0.00 0.00median#> evaluate 100.default 00 5.94 730.86 00 0.00#> evaluate_call 100.00 35.56 94 0.00 0.00mean #> in_dir 100.00 5.94 0.00 0.00#> knit 100.00 5.94 0.00 0.00#> process_file 100.00 5.94 0.00 0.00#> process_group 100.00 5.94 0.00 0.00#> process_group.block 100.00 5.94 0.00 0.00#> withCallingHandlers 100.00 5.94 0.00 0.00#> desc 99.66 5.80 92 0.00 0.00#> eval 99.66 35.22 92 0.00 0.00sort 63#> handle 99.07 66 35.04 92 0.00 0.00sort#> withVisible 99.66 5.92 0.00 0.00#> median 80.47 4.78 0.00 0.00#> median.default 80.47 634.78 0.00 0.00#> mean 74.07 34.04 40 0.34 0.02#> sort 72.73 4.32 0.00 0.00#> sort.int 63default 72.07 73 34.04 5232 0.70 00 20.5400is#> sort.na 14int 72.52 73 04.70 32 1466.52 33 03.7094#> .Call 9 8.13 42 0.44 50 98.13 42 0.4450#> sd 9 8.13 42 0.44 50 0.00 0.00#> var 9 8.13 42 0.44 50 0.00 0.00min 7#> is.na 8.88 08 0.38 48 78.88 08 0.3848#> any 6 5.64 72 0.32 34 65.64 72 0.3234max #> min 5.39 0.26 32 5.39 0.32#> max 4.38 0.26 4.38 0.26#> mean.default 3 1.73 01 0.18 06 31.73 01 0.1806#> <Anonymous> 0.34 0.02 0.00 0.00#> formals 0.34 0.02 0.00 0.00#> match.arg 0.34 0.02 0.00 0.00#> sink 0.34 0.02 0.00 0.00#> sys.function 0.34 0.02 0.00 0.00#> sys.parent 0.34 0.02 0.34 0.02</nowiki>}}
Функция интересна тем, что предоставляет более компактный вывод при той же информативности, что и функция <code>summaryRprof()</code>.
Пакет {{r-package|profr}} предоставляет несколько функций для упрощения процесса профилирования, а также позволяет графически представить результаты профилирования. Результаты работы функции <code>profr</code> представляют собой таблицу, с хронологическим перечислением вызовов. Поэтому при использовании данной функции необходимо использовать однократное выполнение выражения. Вышеприведённый листинг по профилированию работы функции <code>desc()</code> будет выглядеть следующим образом:
{{r-code|code=<nowiki>> library(profr)> prof <- profr(desc(x))> print(prof)Read 3 items #> level g_id t_id f level time start end n leaf time source8 desc #> 23 1 0.04 0. 1 1 desc 00 0.04 1 FALSE <NA>9 median 2 0.02 004 .00 0.02 FALSE statsGlobalEnv10 sd #> 24 2 1 1 median 0.02 0.02 04 1 FALSE 0.04 FALSE stats11 #> 25 3 1 1 median.default 3 0.02 0.00 0.02 04 1 FALSE stats12 var 3 0.02 0.02 0.04 FALSE stats13 #> 26 4 1 1 mean 4 0.02 0.00 0.02 04 1 FALSE base14 .Call 4 0.02 0.02 0.04 TRUE base15 #> 27 5 1 1 sort 5 0.02 0.00 04 1 FALSE 0.02 FALSE 04 base16 #> 28 6 1 1 sort.default 6 0.02 0.00 04 1 FALSE 0.02 FALSE 04 base17 #> 29 7 1 1 sort.int 7 0 0.02 04 1 TRUE 0.00 0.02 TRUE 04 base</nowiki>}}
Содержание столбцов:
Графическое представление результатов профилирования осуществляется с помощью стандартной функции <code>plot()</code> или <code>ggplot()</code>:
{{r-code|code=<nowiki>> ggplot(prof)</nowiki>}}
[[Файл:Profr-ggplot-desc.pngsvg|600px|center]]
== Функция <code>proftable</code> ==
Мы переработали данную функцию, удалив зависимость от пакета {{r-package|plyr}} и существенно ускорив её. Код модифицированного варианта доступен в [http://git.psylab.info/r-scripts/src/master/proftable.R git-репозитории]. Импортировать данный скрипт можно с помощью функции <code>source()</code>:
{{r-code|code=<nowiki>> source("http://git.psylab.info/r-scripts/raw/master/proftable.R")</nowiki>}}
Данная функция работает с лог-файлом, полученным в результате профилирования и используется как дополнение к функции <code>summaryRprof()</code>.
Для более наглядного представления результатов профилирование необходимо провести с включённой опцией <code>line.profiling</code>. Для удобства последующего анализа результатов можно сохранить функцию и её вызов в файл скрипта и вызывать его с помощью функции <code>source()</code>. В нашем примере скрипт для профилирования имеет следующее содержимое:
{{file|lang=r|filename=/tmp/script.R|code=<nowiki>desc <- function(x) {
n <- length(x)
mean <- mean(x)
for (i in seq_len(100)) desc(x)
</nowiki>
}}
 
Данный файл можно сформировать прямо из консоли R:
 
{{r-code|code=<nowiki>script <- paste(tempdir(), "script.R", sep = "/")
dump("desc", file = script)
write("for (i in seq_len(100)) desc(x)", file = script, append = TRUE)
</nowiki>}}
Процедура профилирования осуществляется описанным выше способом:
{{r-code|code=<nowiki>> Rprof(tmp.log, line.profiling = TRUE)> source("/tmp/script.R")> Rprof(NULL)</nowiki>}}
Результаты работы функции <code>proftable()</code> представлены ниже.
{{r-code|code=<nowiki>> proftable(tmp.log)#> Calls:#> RealTime PctTime Call #> 543.464 94 65.667 1#4 > median > median.default > mean > sort > sort.default > sort.int #> 100.268 68 11.333 1#5 > sd > var > .Call 7.143 1#7 > max 0.30 5.804 000 1#4 > median > median.default > is.na 5#> 0.357 28 4.667 1min #> 0.24 4 .000 max #> 0.22 3.667 median > median.default > mean > sort > sort.default > sort.int > is.na 5#> 0.12 2.357 000 1#6 median > min 4median.464 default > mean > sort > sort.default > sort.int > any 1#3 > 0.10 1.667 mean > mean.default 3.571 1#4 > 0.08 1.333 median > median.default > any 3#> 0.125 02 0.333 1#4 > median > median.default > mean > sort > sort.default > sort.int > any 0.446 1#4 > median  #> Files:None /tmp/RtmpAvhCfO/file23b65745e7ba.R#> #> Parent Call: knit > process_file > withCallingHandlers > process_group > process_group.block > call_block > block_exec > in_dir > evaluate > evaluate_call > handle > withCallingHandlers > withVisible > eval > eval > source > withVisible > eval > eval > 1desc #11 > desc #> Total Time: 4.48 6 seconds#> #> Percent of run time represented: 10099.7%</nowiki>}}
Разберём вывод функции <code>proftable()</code>. В первом столбце отображается время выполнения того или иного вызова, во втором --- название вызываемой функции. Далее по цепочке приведена иерархия вызовов. Весь вывод отсортирован по времени выполнения вызовов по убыванию. Вывод наглядно демонстрирует наиболее медленные вызовы вплоть до самого низкого уровня иерархии.
Иногда, чтобы выяснить какой функции принадлежит тот или иной вызов, приходится анализировать содержимое функций. Чтобы увидеть содержимое функции, можно воспользоваться функцией <code>getAnywhere()</code>. Например:
{{r-code|code=<nowiki>getAnywhere(median.default)#> A single object matching ‘median'median.default’ default' was found#> It was found in the following places#> package:stats#> registered S3 method for median from namespace stats#> namespace:stats#> with value#> #> function (x, na.rm = FALSE) #> {#> if (is.factor(x) || is.data.frame(x)) #> stop("need numeric data")#> if (length(names(x))) #> names(x) <- NULL#> if (na.rm) #> x <- x[!is.na(x)]#> else if (any(is.na(x))) #> return(x[FALSE][NA])#> n <- length(x)#> if (n == 0L) #> return(x[FALSE][NA])#> half <- (n + 1L)%/%2L#> if (n%%2L == 1L) #> sort(x, partial = half)[half]#> else mean(sort(x, partial = half + 0L:1L)[half + 0L:1L])#> }#> <bytecode: 0x3c8f8280x37bf0b8>#> <environment: namespace:stats></nowiki>}}
Если искомая функция принадлежит какому-то пакету, то этот пакет должен быть предварительно загружен.
С учётом выявленных «узких» мест нашей функции и проанализировав исходный код функций, мы можем оптимизировать нашу исходную функцию для достижения максимальной производительности. Наш вариант оптимизированной функции будет выглядеть следюущим образом:
{{r-code|code=<nowiki>desc <- function(x) {
n <- length(x)
mean <- sum(x) / n
return(c(n, mean, median, sd, min, max))
}
</nowiki>}}
Вместо функций <code>median()</code> и <code>sd()</code> мы использовали общеизвестные формулы. А для расчёта медианы была взята часть оригинальной функции <code>median.default</code>, в которой вызов функций <code>sort()</code> был заменён на более быстрый вызов <code>.internal(psort)</code>.