Performance
Copy-on-modify: R copies an object when you modify it if it is referenced elsewhere.
34.1 Measure first
system.time(for (i in 1:1e6) sqrt(i))
bench::mark(vectorized = sqrt(1:1e5), loop = { r <- numeric(1e5); for (i in 1:1e5) r[i] <- sqrt(i); r })
microbenchmark::microbenchmark(sum(x), Reduce(`+`, x), times = 100)
Rprof("prof.out"); slow_fun(); Rprof(NULL); summaryRprof("prof.out")
profvis::profvis({ slow_fun() }) # interactive flame graph34.2 Speed rules
Vectorize: x * 2 instead of loops; rowSums(), colMeans(), cumsum(), pmin(), pmax(), ifelse(), tabulate().
Pre-allocate results (numeric(n), vector("list", n)); never grow objects in a loop (c(), rbind() in loops is quadratic).
Use matrices instead of data frames for numeric computation.
Avoid repeated work inside loops; hoist invariants.
Use specialized fast packages: data.table, collapse, Rfast, matrixStats, vctrs, fastmap.
Use vapply over sapply (type stable, slightly faster).
Bind many data frames once: do.call(rbind, list) / dplyr::bind_rows(list) / data.table::rbindlist(list).
Use Rcpp for unavoidable loops (§36).
Parallelize independent work (§37).
Use efficient file formats (Parquet, fst, qs2) and lazy query engines (Arrow, DuckDB).
34.3 Memory
Copy-on-modify: R copies an object when you modify it if it is referenced elsewhere. Modifying a column of a data frame copies the column, not the whole frame (shallow copy). Environments, R6 objects and data.table modify in place.
object.size(x); format(object.size(x), units = "MB")
lobstr::obj_size(x); lobstr::ref(x) # shared references
tracemem(x) # prints when x is copied
gc() # trigger garbage collection & report memory
x <- NULL; rm(x)34.4 Byte compilation & ALTREP
Functions are byte-compiled automatically by the JIT (compiler::cmpfun). ALTREP lets R represent 1:1e9 without allocating memory, and enables memory-mapped/lazy vectors (used by vroom, arrow).