Изменения

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

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

13 400 байтов добавлено, 11:37, 27 января 2014
Новая страница: «== Пакет base == Описанные выше функции {{Inline-code|system.time()|lang="rsplus"}}, {{Inline-code|benchmark()|lang="rsplus"}}, {{Inline…»
== Пакет base ==

Описанные выше функции {{Inline-code|system.time()|lang="rsplus"}}, {{Inline-code|benchmark()|lang="rsplus"}}, {{Inline-code|microbenchmark()|lang="rsplus"}} позволяют оценить общее время выполнения выражения и нивелировать возможные вариации за счет множества попыток, однако для более полной оценки и определения стратегии оптимизации кода необходимо также выявлять и «узкие» места в выполняемом коде. К подобным «узким» местам можно отнести те вызовы, которые занимают наибольшее количество времени или расходуют больше всего памяти (при профилирования расхода памяти). Существуют специальные пакеты, предназначенные для организации профилирования или для более наглядной демонстрации результатов, однако далее пойдет речь о функции {{Inline-code|Rprof()|lang="rsplus"}} из пакета {{Inline-code|base|lang="rsplus"}}.

Принцип работы функции {{Inline-code|Rprof()|lang="rsplus"}} таков: с заданным интервалом (аргумент {{Inline-code|interval|lang="rsplus"}}) делаются «снимки» вызовов и записываются в файл. Следует помнить о том, что {{Inline-code|Rprof()|lang="rsplus"}} делает «снимки» каждые 0.02 секунды (значение по умолчанию), поэтому если выражение выполняется быстрее, то {{Inline-code|Rprof()|lang="rsplus"}} не сможет его отследить. В таком случае периодичность «снимков» можно задать вручную с помощью аргумента {{Inline-code|interval|lang="rsplus"}}. Также можно увеличить объём данных, чтобы код выполнялся дольше.

Ещё одной специфической особенностью профилирования является то, что при профилирования записываются абсолютно все вызовы. Например, если вы используется функцию {{Inline-code|replicate()|lang="rsplus"}} для организации повторных выполнений выражения, то вызовы {{Inline-code|replicate()|lang="rsplus"}} также будут записаны в лог, что может затруднить его анализ.

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

В качестве примера ниже представлены шаги процесса профилирования функции, которая рассчитывает первичные описательные статистики (среднее, медиану, стандартное отклонение, минимум и максимум) по сгенерированным $10^6$ нормально распределённым значениям. Объявим данную функцию:

<syntaxhighlight lang="rsplus">
desc <- function(x) {
n <- length(x)
mean <- mean(x)
median <- median(x)
sd <- sd(x)
min <- min(x)
max <- max(x)
return(c(n, mean, median, sd, min, max))
}
</syntaxhighlight>

Шаг 1. Создание временного файла для записи лога профилирования (необязательныо)

<syntaxhighlight lang="rsplus">
tmp.log <- tempfile(pattern = "prof-", fileext = ".log")
</syntaxhighlight>

Шаг 2. Собственно процедура профилирования:

<syntaxhighlight lang="rsplus">
# Включаем профилирование
Rprof(tmp.log)
# Выполняем код
for (i in seq_len(100))
invisible(desc(nvec))
# отключаем профилирование
Rprof(NULL)
</syntaxhighlight>

Шаг 3. Вывод результатов:

<syntaxhighlight lang="rsplus">
summaryRprof(tmp.log)
</syntaxhighlight>

Шаг 4. Удаляем временнвый файл:

<syntaxhighlight lang="rsplus">
unlink(tmp.log)
</syntaxhighlight>

Рассмотрим результаты профилирования функции {{Inline-code|desc()|lang="rsplus"}}:

<syntaxhighlight lang="rsplus">
summaryRprof(tmp.log)
print(sumprof.desc)
</syntaxhighlight>

Результатом подобных операций является 2 таблицы, отсортированные по разным основаниям: по времени выполнения функции ({{Inline-code|self.time|lang="rsplus"}}) и общему времени выполнения функции ({{Inline-code|total.time|lang="rsplus"}}). В столбцах представлены абсолютные и относительные (в процентах) показатели времени выполнения.

При профилировании скриптов включение опции {{Inline-code|line.profiling|lang="rsplus"}} проставляет номера строк в которых производился вызов, что позволяет проанализировать производительность всего кода:

<syntaxhighlight lang="rsplus">
Rprof(tmp.log, line.profiling = TRUE)
source("/path/scritpt.R")
Rprof(NULL)
summaryRprof(tmp.log)
unlink(tmp.log)
</syntaxhighlight>

== Пакет proftools ==

Также может оказаться полезной функция {{Inline-code|flatProfile()|lang="rsplus"}} из пакета {{Inline-code|proftools|lang="rsplus"}}. Данная функция выводит таблицу, сходную с таблицей «by.total» возвращаемую функцией {{Inline-code|summaryRprof()|lang="rsplus"}}, в одну и сортирует их по общему времени выполнения в процентах или по времени выполнения функции в процентах, если указать {{Inline-code|byTotal = FALSE|lang="rsplus"}}:

<syntaxhighlight lang="rsplus">
library(proftools)
flatProfile(readProfileData(tmp.log))
print(flatprof.desc)
</syntaxhighlight>

Функция интересна тем, что предоставляет более компактный вывод при той же информативности, что и функция {{Inline-code|summaryRprof()|lang="rsplus"}}.

== Пакет profr ==

Пакет {{Inline-code|profr|lang="rsplus"}} предоставляет несколько функций для упрощения процесса профилирования, а также позволяет графически представить результаты профилирования. Результаты работы функции {{Inline-code|profr|lang="rsplus"}} представляют собой таблицу, с хронологическим перечислением вызовов. Поэтому при использовании данной функции необходимо использовать однократное выполнение выражения. Вышеприведённый листинг по профилированию работы функции {{Inline-code|desc()|lang="rsplus"}} будет выглядеть следующим образом:

<syntaxhighlight lang="rsplus">
library(profr)
prof <- profr(invisible(desc(nvec)))
prof <- profr.desc
print(prof)
</syntaxhighlight>

Содержание столбцов:
\begin{itemize}
\item «f» --- название функции;
\item «level» --- уровень в иерархии вызовов;
\item «time» --- общее время выполнения функции;
\item «start» --- время начала выполнения функции;
\item «end» --- время окончания выполнения функции;
\item «leaf» --- TRUE, если функция вызывается другими функциями;
\item «source» --- название пакеты, содержащего данную функцию.
\end{itemize}

Графическое представление результатов профилирования осуществляется с помощью стандартной функции {{Inline-code|plot()|lang="rsplus"}}:

<syntaxhighlight lang="rsplus">
plot(prof)
</syntaxhighlight>

== Функция proftable() ==

Рассмотрим ещё один способ представления результатов профилирования --- функция {{Inline-code|proftable()|lang="rsplus"}}, написанная Noam Ross. Для импорта данной файла скрипта нам понадобятся пакеты {{Inline-code|devtools|lang="rsplus"}} и {{Inline-code|digest|lang="rsplus"}}, т.к. штатная функция {{Inline-code|source()|lang="rsplus"}} не поддерживает протокол https. Итак, импортируем скрипт с функцией {{Inline-code|proftable()|lang="rsplus"}}:

<syntaxhighlight lang="rsplus">
library(devtools)
library(digest)
source_url("https://raw.github.com/noamross/noamtools/master/R/proftable.R")
</syntaxhighlight>

Для корректной работы функции {{Inline-code|proftable()|lang="rsplus"}} необходимо провести профилирование с включённой опцией {{Inline-code|line.profiling|lang="rsplus"}}. Для удобства последующего анализа результатов сохраним нашу функцию и её вызов в файл:

<syntaxhighlight lang="rsplus">
tmp.R <- tempfile(fileext = ".R")
dump("desc", tmp.R)
cat("for (i in seq_len(100))\n invisible(desc(nvec))",
file = tmp.R, fill = TRUE, append = TRUE)
</syntaxhighlight>

Процедура профилирования осуществляется описанным выше способом:

<syntaxhighlight lang="rsplus">
Rprof(tmp.log, line.profiling = TRUE)
source(tmp.R)
Rprof(NULL)
unlink(tmp.R)
unlink(tmp.log)
</syntaxhighlight>

Для работы функции {{Inline-code|proftable()|lang="rsplus"}} также необходим пакет {{Inline-code|plyr|lang="rsplus"}}.

<syntaxhighlight lang="rsplus">
tmp.log <- paste0(LOGDIR, "/desc-lineprof.log")
library(plyr)
proftable(tmp.log)
</syntaxhighlight>

Разберём вывод функции {{Inline-code|proftable()|lang="rsplus"}}. В первом столбце отображается время выполнения того или иного вызова, во втором --- название вызываемой функции. Далее по цепочке приведена иерархия вызовов. Весь вывод отсортирован по времени выполнения вызовов по убыванию. Вывод наглядно демонстрирует наиболее медленные вызовы вплоть до самого низкого уровня иерархии.

Обратите внимание, что функция {{Inline-code|proftable()|lang="rsplus"}} имеет второй аргумент --- {{Inline-code|lines|lang="rsplus"}}, который отвечает за количество строк в выводе.

== Выводы ==

Из примера выше мы можем сделать вывод, что наиболее медленной функцией из состава функции {{Inline-code|desc()|lang="rsplus"}} является функция {{Inline-code|median()|lang="rsplus"}}, которая через некоторую последовательность вызовов вызывает функцию {{Inline-code|sort.int()|lang="rsplus"}}. Именно функция {{Inline-code|sort.int()|lang="rsplus"}} является наиболее «узким» местом всех расчётов.

Иногда, чтобы выяснить какой функции принадлежит тот или иной вызов, приходится анализировать содержимое функций. Чтобы увидеть содержимое функции, можно воспользоваться функцией {{Inline-code|getAnywhere()|lang="rsplus"}}. Например:

<syntaxhighlight lang="rsplus">
getAnywhere(median.default)
</syntaxhighlight>

Если искомая функция принадлежит какому-то пакету, то этот пакет должен быть предварительно загружен.

Навигация