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 graph

34.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).