Profiling Code with Rprof and profvis
Identify which functions consume the most time in your scripts.
Profiling Code with Rprof and profvis is a free R Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the R Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
What Is Profiling?
Timing tells you how long code runs. Profiling tells you where the time is spent inside that code. R's profiler samples the call stack at regular intervals to build a statistical picture of which functions are expensive.
Two main tools: the built-in Rprof() and the interactive profvis package.
Starting and Stopping Rprof()
Rprof('output.prof', interval = 0.01) starts the profiler. It writes call-stack samples to a file every 10 ms. Run your slow code, then call Rprof(NULL) to stop recording.
The interval argument controls sampling frequency in seconds — smaller values give more resolution but larger output files.
# 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 profilingReading Results with summaryRprof()
summaryRprof('output.prof') parses the profiling file and returns a list with two data frames:
- by.self — time spent in each function itself (excluding callees)
- by.total — total time including all functions called by that function
Sort by self.pct to find hot spots.
# 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.0Self vs Total Time
Understanding self vs total time is critical for profiling:
- Self time — time the function spent running its own lines (not waiting on callees)
- Total time — self time plus all time spent in functions it called
A function with high total but low self time is slow because of what it calls, not its own logic. Optimize the callee, not the caller.
# 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
')Introducing profvis
profvis wraps Rprof() and provides an interactive HTML flame graph in RStudio or a browser. It is dramatically easier to read than raw summaryRprof() output.
Install once with install.packages('profvis'), then wrap your code with 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)
# })Reading the profvis Flame Graph
The profvis output has two panels:
- Flame graph — horizontal bars where width = time; nested bars = call stack depth
- Data table — sortable view of self and total time per function and source line
Wide bars at the bottom of the flame graph are the most expensive callers. Tall stacks indicate deep call chains.
# 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
')Identifying Hot Spots
After viewing the profvis output, identify hot spots by looking for functions that are wide in the flame graph and have high self-time. Common culprits in R:
- Unvectorized loops doing element-by-element work
- Repeated
rbind()orc()growing objects in a loop - Repeated regex or string parsing in tight loops
# 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
')Profiling Memory Allocations
Rprof can also track memory allocations with memory.profiling = TRUE. profvis shows a memory panel alongside timing, letting you spot functions that allocate large temporary objects — a key source of garbage-collection pauses.
# 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
')Profiling a Real Pipeline
Apply profiling systematically to a data pipeline: wrap the entire pipeline in profvis({}), identify the slowest stage, optimize it, then re-profile to confirm improvement. Never optimize blindly.
# 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
')Limitations of Rprof Sampling
Rprof uses statistical sampling, so very fast functions (faster than the sampling interval) may not appear. For micro-benchmarks of tiny expressions, use the microbenchmark package instead.
Also, Rprof does not profile C/C++ code below the R interface — only R-level call stacks are visible.
# 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
')Best Practices for R Profiling
Follow these practices to get reliable profiling results:
- Profile with realistic data sizes — small inputs hide the actual bottleneck
- Run warm-up iterations before profiling to exclude one-time setup costs
- Profile in a clean R session to avoid interference from loaded packages
- Use
profvisfor exploration; usesummaryRproffor CI/automated reports
# 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
')Quick Check: Rprof Self vs Total
A function shows total.pct = 95% but self.pct = 3% in summaryRprof() output. What does this tell you?
Profiling Tools Recap
R gives you a two-level profiling toolkit:
Rprof('file.prof', interval=0.01)+Rprof(NULL)+summaryRprof()— built-in, scriptable, CI-friendlyprofvis({...})— interactive flame graph with source-line annotation and memory tracking
The correct workflow is always: measure first, identify the hottest spot, optimize only that, then re-measure to confirm the gain.
Frequently asked questions
Is the “Profiling Code with Rprof and profvis” lesson free?
Yes — the full text of “Profiling Code with Rprof and profvis” is free to read here on the web, and the R Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the R Academy course, upgrade to CoddyKit PRO.
What will I learn in “Profiling Code with Rprof and profvis”?
Identify which functions consume the most time in your scripts. You practise R Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start R Academy?
No prior experience is required. R Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Profiling Code with Rprof and profvis” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this R Academy lesson?
Yes. Every R Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- system.time() and proc.time()
- Profiling Code with Rprof and profvis
- Vectorization for Speed
- Benchmarking with microbenchmark