0Pricing
R Academy · Урок

Профилирование кода с помощью Rprof и profvis

Определяйте, какие функции занимают больше всего времени в Ваших скриптах

«Профилирование кода с помощью Rprof и profvis» — бесплатный урок R Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения R Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс R Academy содержит 4 уроков всего.

Что такое профилирование?

Измерение времени показывает, сколько времени выполняется код. Профилирование показывает, где именно внутри этого кода тратится время. Профилировщик R через равные интервалы снимает образцы стека вызовов и формирует статистическую картину того, какие функции требуют больше всего ресурсов.

Два основных инструмента — встроенный Rprof() и интерактивный пакет profvis.

Запуск и остановка Rprof()

Rprof('output.prof', interval = 0.01) запускает профилировщик. Он записывает образцы стека вызовов в файл каждые 10 мс. Выполните медленный код, затем вызовите Rprof(NULL), чтобы остановить запись.

Аргумент interval задаёт частоту снятия образцов в секундах: меньшие значения повышают детализацию, но увеличивают размер файлов результата.

# Pattern — do not run Rprof inside knitr/Quarto
# Rprof('my_profile.prof', interval = 0.01)
#
# slow_function <- function(n) {
#   x <- numeric(n)
#   for (i in seq_len(n)) x[i] <- sqrt(i)
#   sum(x)
# }
# slow_function(500000)
#
# Rprof(NULL)  # stop profiling

Чтение результатов с помощью summaryRprof()

summaryRprof('output.prof') разбирает файл профилирования и возвращает список с двумя датафреймами:

  • by.self — время, затраченное каждой функцией непосредственно (без вызываемых ею функций)
  • by.total — общее время, включая все функции, вызванные этой функцией

Отсортируйте данные по self.pct, чтобы найти самые нагруженные участки.

# After Rprof() run:
# prof <- summaryRprof('my_profile.prof')
# head(prof$by.self)
#
# Example output columns:
#            self.time self.pct total.time total.pct
# slow_fn        1.22     61.0       1.98      99.0
# sqrt           0.76     38.0       0.76      38.0
# sum            0.02      1.0       0.02       1.0

Собственное и общее время

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

  • Собственное время — время, которое функция выполняла свои строки (не ожидая вызываемые функции)
  • Общее время — собственное время плюс всё время, затраченное вызываемыми ею функциями

Функция с высоким общим временем, но низким собственным медленна из-за того, что она вызывает, а не из-за собственной логики. Оптимизируйте вызываемую функцию, а не вызывающую.

# Conceptual example:
# wrapper() -> process_data() -> slow_sort()
#
# total.pct: wrapper=100, process_data=90, slow_sort=85
# self.pct:  wrapper=5,   process_data=5,  slow_sort=85
#
# => slow_sort is the real bottleneck to fix.
cat('High total + low self => the culprit is a callee function
')

Знакомство с profvis

profvis использует Rprof() и предоставляет интерактивную пламенную диаграмму в формате HTML в RStudio или браузере. Читать её значительно проще, чем необработанный результат summaryRprof().

Установите пакет один раз с помощью install.packages('profvis'), затем оберните код в profvis({...}).

# library(profvis)
#
# profvis({
#   n <- 200000
#   x <- numeric(n)
#   for (i in seq_len(n)) x[i] <- log(i)
#   total <- sum(x)
#   sorted <- sort(x)
# })

Как читать пламенную диаграмму profvis

Результат profvis содержит две панели:

  • Пламенная диаграмма — горизонтальные полосы, ширина которых соответствует времени; вложенные полосы показывают глубину стека вызовов
  • Таблица данных — сортируемое представление собственного и общего времени для каждой функции и исходной строки

Широкие полосы в нижней части пламенной диаграммы соответствуют самым затратным вызывающим функциям. Высокие стеки указывают на длинные цепочки вызовов.

# Reading the flame graph:
# - Each horizontal bar = one function on the call stack
# - Width proportional to time spent
# - Bottom = outermost caller, top = deepest callee
# - Click a bar to zoom in
# - 'Memory' tab shows allocation by line
cat('profvis shows self time per source line — invaluable for tight loops
')

Поиск самых нагруженных участков

Просмотрев результат profvis, найдите самые нагруженные участки: ищите функции, которые занимают большую ширину на пламенной диаграмме и имеют большое собственное время. Распространённые причины замедления в R:

  • Не векторизованные циклы, выполняющие операции по одному элементу
  • Многократные вызовы rbind() или c(), увеличивающие объекты в цикле
  • Многократный разбор регулярных выражений или строк в тесных циклах
# Before fix — growing vector in loop (common hot spot)
# profvis reveals repeated reallocations:
# result <- c()
# for (i in 1:50000) result <- c(result, i^2)
#
# After fix — pre-allocated:
# result <- numeric(50000)
# for (i in 1:50000) result[i] <- i^2
cat('Pre-allocation eliminates the most common loop hot spot
')

Профилирование выделения памяти

Rprof также может отслеживать выделение памяти с помощью memory.profiling = TRUE. profvis показывает панель памяти рядом с данными о времени, позволяя находить функции, создающие крупные временные объекты — один из основных источников пауз сборки мусора.

# Memory profiling with Rprof:
# Rprof('mem.prof', interval = 0.01, memory.profiling = TRUE)
# ... slow code ...
# Rprof(NULL)
# prof <- summaryRprof('mem.prof', memory = 'both')
# head(prof$by.self)
#
# profvis also shows mem delta per line automatically
cat('Memory profiling pinpoints allocation hot spots causing GC pauses
')

Профилирование реального конвейера

Применяйте профилирование к конвейеру обработки данных систематически: оберните весь конвейер в profvis({}), найдите самый медленный этап, оптимизируйте его, а затем повторите профилирование, чтобы подтвердить улучшение. Никогда не оптимизируйте вслепую.

# Workflow:
# 1. profvis({ full_pipeline() })   => identify Stage 3 is 80% of time
# 2. Rewrite Stage 3 (vectorize / use data.table)
# 3. profvis({ full_pipeline() })   => confirm Stage 3 now < 10%
# 4. system.time({ full_pipeline() }) => confirm overall speedup
cat('Profile -> identify -> fix -> re-profile is the correct cycle
')

Ограничения выборки Rprof

Rprof использует статистическую выборку, поэтому очень быстрые функции (работающие быстрее интервала выборки) могут не попасть в результат. Для микротестов небольших выражений вместо этого используйте пакет microbenchmark.

Кроме того, Rprof не профилирует код C/C++ ниже интерфейса R — видны только стеки вызовов на уровне R.

# Rprof interval = 0.01s => functions faster than 10ms may not appear
# For sub-millisecond work use microbenchmark:
#   microbenchmark(expr1, expr2, times = 1000L)
#
# For C-level profiling use external tools:
#   - Instruments (macOS)
#   - perf (Linux)
cat('Rprof is for R-level profiling; use microbenchmark for micro-timing
')

Рекомендации по профилированию R

Следуйте этим рекомендациям, чтобы получать надёжные результаты профилирования:

  • Профилируйте реалистичные объёмы данных — небольшие входные данные скрывают настоящее узкое место
  • Перед профилированием выполните несколько прогревочных итераций, чтобы исключить затраты на однократную настройку
  • Профилируйте в чистом сеансе R, чтобы избежать помех от загруженных пакетов
  • Используйте profvis для исследования, а summaryRprof — для отчётов CI и автоматизированных отчётов
# Clean session profiling checklist:
# 1. Restart R (Ctrl+Shift+F10 in RStudio)
# 2. Load only required packages
# 3. Run once to warm up
# 4. profvis({ ... })  on second run
# 5. Compare before/after with system.time()
cat('Always profile with realistic data in a clean R session
')

Быстрая проверка: собственное и общее время в Rprof

В результате summaryRprof() для функции указано total.pct = 95%, но self.pct = 3%. О чём это говорит?

Повторение: инструменты профилирования

R предоставляет двухуровневый набор инструментов профилирования:

  • Rprof('file.prof', interval=0.01) + Rprof(NULL) + summaryRprof() — встроенный, пригодный для скриптов и удобный для CI
  • profvis({...}) — интерактивная пламенная диаграмма с аннотациями исходных строк и отслеживанием памяти

Правильный рабочий процесс всегда таков: сначала измерьте, найдите самый нагруженный участок, оптимизируйте только его, затем снова измерьте результат, чтобы подтвердить улучшение.

Часто задаваемые вопросы

Урок «Профилирование кода с помощью Rprof и profvis» бесплатный?

Да — полный текст урока «Профилирование кода с помощью Rprof и profvis» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс R Academy, подпишись на CoddyKit PRO. Курс R Academy содержит 4 уроков всего.

Чему я научусь в уроке «Профилирование кода с помощью Rprof и profvis»?

Определяйте, какие функции занимают больше всего времени в Ваших скриптах Ты практикуешь R Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать R Academy?

Предыдущий опыт не требуется. R Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «Профилирование кода с помощью Rprof и profvis»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке R Academy?

Да. Каждый урок R Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. system.time() и proc.time()
  2. Профилирование кода с помощью Rprof и profvis
  3. Векторизация для ускорения
  4. Бенчмаркинг с помощью microbenchmark
← Назад к R Academy