Performance and Profiling
Prerequisites: Loops, The apply family
The first rule of performance work: measure before you change anything. Most R code is fast enough; when it isn't, the slow part is usually a few specific lines.
Measuring
x <- runif(1e6)
loop_sum <- function(v) {
total <- 0
for (value in v) total <- total + value
total
}
t_loop <- system.time(loop_sum(x))["elapsed"]
t_vec <- system.time(sum(x))["elapsed"]
t_vec <= t_loop
elapsed
TRUE
system.time() times an expression. For reliable comparisons of small
pieces of code, the bench package runs each many times:
bench::mark(loop_sum(x), sum(x)). To find where a long script spends
its time, profvis (profvis::profvis({ ... })) shows an interactive
breakdown by line.
The big wins
1. Vectorize. Built-in vectorized functions (sum, cumsum,
ifelse, paste, rowSums, arithmetic) run in fast compiled code. A
vectorized version is often 10–100× faster than an R loop.
2. Don't grow objects in a loop. Appending (result <- c(result, x))
copies the whole vector every time. Preallocate instead:
n <- 5
squares <- numeric(n)
for (i in seq_len(n)) squares[i] <- i^2
squares
[1] 1 4 9 16 25
3. Use the right tool for big data.
- data.table — extremely fast grouping, joining, and reading of large
files (
fread), with a concise syntax:dt[, .(avg = mean(salary)), by = team]. - arrow — reads Parquet files and datasets larger than memory, working with dplyr verbs.
- duckdb — an in-process analytical database that runs dplyr or SQL on large files very fast.
4. Move hot loops to C++. When a loop truly can't be vectorized, the Rcpp package lets you write that function in C++ and call it from R.
5. Run in parallel. Independent tasks (simulations, model fits per
group) can use several CPU cores — for example with the future and
furrr packages: furrr::future_map(...) is a parallel map().
Memory
object.size(x) shows how much memory an object uses. R copies objects
when they're modified (copy-on-modify), so very large data frames that
are changed repeatedly can use a lot of memory — another case where
data.table (which modifies in place) or a database helps.
Common mistakes
- Optimizing code that isn't the bottleneck. Profile first.
- Rewriting readable code for tiny gains. Fast enough and clear beats fastest and unreadable.