Reading — step 1 of 4
Learn
R is interpreted and dynamic — slow by default. But the parts that matter (linear algebra, summary stats) call optimized C/Fortran. The art is keeping your hot path in those primitives.
Profile before optimizing. Rprof is built in:
Rprof("profile.out")
slow_function()
Rprof(NULL)
summaryRprof("profile.out")
profvis (interactive HTML profiler):
library(profvis)
profvis({
x <- runif(1e6)
y <- sapply(x, function(v) v^2) # bad — slow loop
z <- x^2 # good — vectorized
})
Shows flame graph + line-by-line time + memory.
microbenchmark for comparing alternatives:
library(microbenchmark)
x <- runif(10000)
microbenchmark(
sapply_method = sapply(x, function(v) v^2),
vector_method = x^2,
times = 100
)
Reports min/mean/median/max for each. Vector ops typically 100-1000x faster.
Performance principles:
1. Vectorize. Already discussed — replaces loops with C-level ops.
2. Avoid copying. R uses copy-on-modify. Each result[i] <- v can trigger a full copy:
# slow — may copy result on each iteration
result <- c()
for (i in 1:n) result <- c(result, i^2)
# fast — pre-allocate
result <- numeric(n)
for (i in 1:n) result[i] <- i^2
Better: vectorize entirely.
3. Use efficient data structures.
data.table— much faster than data.frame for >100k rowsMatrixpackage for sparse matricesbigstatsr/bigmemoryfor out-of-RAM datasets
4. Compile hot loops with Rcpp.
library(Rcpp)
cppFunction('
int sum_squares(int n) {
int s = 0;
for (int i = 1; i <= n; i++) s += i * i;
return s;
}
')
sum_squares(1000)
Mixes C++ with R. Often 10-100x faster than equivalent R loop.
5. Parallelize.
library(parallel)
# Use cores
results <- mclapply(1:1000, slow_fn, mc.cores = 4)
# Or with future:
library(future.apply)
plan(multisession, workers = 4)
results <- future_lapply(1:1000, slow_fn)
6. Replace S4/refclass dispatch in hot loops. Method dispatch isn't free.
7. Use which.max, which.min, pmax, pmin instead of writing loops:
# slow
best <- 1
for (i in 2:length(x)) if (x[i] > x[best]) best <- i
# fast
best <- which.max(x)
Memory profiling:
library(pryr)
object_size(x) # actual size in bytes
mem_used() # total R memory
# Track allocations during a block:
library(profmem)
p <- profmem({
big <- numeric(1e7)
big2 <- big * 2
})
print(p)
bench package — modern microbenchmark:
library(bench)
bench::mark(
sapply = sapply(1:1000, function(x) x^2),
vector = (1:1000)^2,
iterations = 100
)
Reports memory allocations too — often the bottleneck, not CPU.
Common pitfalls:
- Growing vectors in loops with
c() - Calling
data.frame[i, j]in tight loops (slow indexing) applyon data frames (converts to matrix internally — slow if mixed types)- Using
rownames(df)(slow lookup)
Real-world: profile, find the bottleneck (it's almost never where you think), apply the relevant fix from above.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…