R Performance Tuning Best Practices¶
Objective: Master senior-level R performance tuning patterns for production systems. When you need to optimize R code for speed and efficiency, when you want to handle large datasets effectively, when you need enterprise-grade performance patternsโthese best practices become your weapon of choice.
Core Principles¶
- Profiling First: Always profile before optimizing
- Data Structures: Choose appropriate data structures for the task
- Vectorization: Leverage R's vectorized operations
- Memory Management: Optimize memory usage and garbage collection
- Parallel Processing: Utilize multiple cores when beneficial
Profiling and Benchmarking¶
Code Profiling¶
# R/01-profiling.R
#' Comprehensive code profiling
#'
#' @param code_expression Code expression to profile
#' @param profiling_type Type of profiling
#' @return Profiling results
profile_code <- function(code_expression, profiling_type = "line") {
if (profiling_type == "line") {
return(profile_line_by_line(code_expression))
} else if (profiling_type == "function") {
return(profile_function_calls(code_expression))
} else if (profiling_type == "memory") {
return(profile_memory_usage(code_expression))
}
}
#' Profile code line by line
#'
#' @param code_expression Code expression
#' @return Line-by-line profiling results
profile_line_by_line <- function(code_expression) {
library(profvis)
# Create temporary file for code
temp_file <- tempfile(fileext = ".R")
writeLines(deparse(substitute(code_expression)), temp_file)
# Profile the code
prof_results <- profvis({
eval(code_expression)
})
# Clean up
unlink(temp_file)
return(prof_results)
}
#' Profile function calls
#'
#' @param code_expression Code expression
#' @return Function call profiling results
profile_function_calls <- function(code_expression) {
library(profr)
# Profile function calls
prof_results <- profr({
eval(code_expression)
})
return(prof_results)
}
#' Profile memory usage
#'
#' @param code_expression Code expression
#' @return Memory profiling results
profile_memory_usage <- function(code_expression) {
library(pryr)
# Get memory before
memory_before <- mem_used()
# Execute code
result <- eval(code_expression)
# Get memory after
memory_after <- mem_used()
# Calculate memory usage
memory_usage <- memory_after - memory_before
return(list(
memory_before = memory_before,
memory_after = memory_after,
memory_usage = memory_usage,
result = result
))
}
#' Benchmark multiple implementations
#'
#' @param implementations List of implementations to benchmark
#' @param data Data to test with
#' @param iterations Number of iterations
#' @return Benchmark results
benchmark_implementations <- function(implementations, data, iterations = 100) {
library(microbenchmark)
# Create benchmark expressions
benchmark_exprs <- lapply(implementations, function(impl) {
substitute(impl(data))
})
# Run benchmark
benchmark_results <- microbenchmark(
list = benchmark_exprs,
times = iterations,
unit = "ms"
)
return(benchmark_results)
}
#' Analyze benchmark results
#'
#' @param benchmark_results Benchmark results
#' @return Analysis of benchmark results
analyze_benchmark_results <- function(benchmark_results) {
# Calculate summary statistics
summary_stats <- summary(benchmark_results)
# Find fastest implementation
fastest_idx <- which.min(summary_stats$median)
fastest_implementation <- levels(benchmark_results$expr)[fastest_idx]
# Calculate relative performance
relative_performance <- summary_stats$median / min(summary_stats$median)
return(list(
summary = summary_stats,
fastest = fastest_implementation,
relative_performance = relative_performance
))
}
Performance Monitoring¶
# R/01-profiling.R (continued)
#' Monitor system performance
#'
#' @param duration Monitoring duration in seconds
#' @param interval Monitoring interval in seconds
#' @return System performance data
monitor_system_performance <- function(duration = 60, interval = 1) {
performance_data <- data.frame(
timestamp = numeric(0),
cpu_usage = numeric(0),
memory_usage = numeric(0),
gc_count = numeric(0)
)
start_time <- Sys.time()
while (as.numeric(Sys.time() - start_time) < duration) {
# Get current performance metrics
current_time <- Sys.time()
cpu_usage <- get_cpu_usage()
memory_usage <- get_memory_usage()
gc_count <- get_gc_count()
# Add to performance data
performance_data <- rbind(performance_data, data.frame(
timestamp = as.numeric(current_time),
cpu_usage = cpu_usage,
memory_usage = memory_usage,
gc_count = gc_count
))
# Wait for next interval
Sys.sleep(interval)
}
return(performance_data)
}
#' Get CPU usage
#'
#' @return CPU usage percentage
get_cpu_usage <- function() {
# This is a simplified version - in practice, you'd use system-specific methods
system_info <- system("top -l 1 | grep 'CPU usage'", intern = TRUE)
if (length(system_info) > 0) {
cpu_match <- regmatches(system_info, regexpr("[0-9.]+%", system_info))
if (length(cpu_match) > 0) {
return(as.numeric(gsub("%", "", cpu_match[1])))
}
}
return(0)
}
#' Get memory usage
#'
#' @return Memory usage in MB
get_memory_usage <- function() {
memory_info <- gc()
return(sum(memory_info[, 2]))
}
#' Get garbage collection count
#'
#' @return Garbage collection count
get_gc_count <- function() {
gc_info <- gc()
return(sum(gc_info[, 1]))
}
Data Structure Optimization¶
Efficient Data Structures¶
# R/02-data-structures.R
#' Optimize data structures for performance
#'
#' @param data Data to optimize
#' @param optimization_type Type of optimization
#' @return Optimized data
optimize_data_structures <- function(data, optimization_type) {
switch(optimization_type,
"data_table" = convert_to_data_table(data),
"matrix" = convert_to_matrix(data),
"sparse" = convert_to_sparse(data),
"factor" = optimize_factors(data),
stop("Unsupported optimization type: ", optimization_type)
)
}
#' Convert to data.table
#'
#' @param data Data frame
#' @return data.table object
convert_to_data_table <- function(data) {
library(data.table)
# Convert to data.table
dt <- as.data.table(data)
# Set key for faster lookups
if (ncol(dt) > 0) {
setkey(dt, names(dt)[1])
}
return(dt)
}
#' Convert to matrix
#'
#' @param data Data frame
#' @return Matrix object
convert_to_matrix <- function(data) {
# Convert to matrix
matrix_data <- as.matrix(data)
# Set row and column names
rownames(matrix_data) <- rownames(data)
colnames(matrix_data) <- colnames(data)
return(matrix_data)
}
#' Convert to sparse matrix
#'
#' @param data Data frame
#' @return Sparse matrix object
convert_to_sparse <- function(data) {
library(Matrix)
# Convert to sparse matrix
sparse_data <- as(data, "sparseMatrix")
return(sparse_data)
}
#' Optimize factors
#'
#' @param data Data frame
#' @return Data with optimized factors
optimize_factors <- function(data) {
optimized_data <- data
# Convert character columns to factors
for (col in names(data)) {
if (is.character(data[[col]])) {
optimized_data[[col]] <- as.factor(data[[col]])
}
}
# Remove unused factor levels
for (col in names(optimized_data)) {
if (is.factor(optimized_data[[col]])) {
optimized_data[[col]] <- droplevels(optimized_data[[col]])
}
}
return(optimized_data)
}
Memory Optimization¶
# R/02-data-structures.R (continued)
#' Optimize memory usage
#'
#' @param data Data to optimize
#' @param optimization_type Type of memory optimization
#' @return Memory-optimized data
optimize_memory_usage <- function(data, optimization_type) {
switch(optimization_type,
"remove_duplicates" = remove_duplicates(data),
"compress_data" = compress_data(data),
"lazy_loading" = enable_lazy_loading(data),
"chunk_processing" = enable_chunk_processing(data),
stop("Unsupported optimization type: ", optimization_type)
)
}
#' Remove duplicate rows
#'
#' @param data Data frame
#' @return Data without duplicates
remove_duplicates <- function(data) {
# Remove duplicate rows
unique_data <- unique(data)
return(unique_data)
}
#' Compress data
#'
#' @param data Data frame
#' @return Compressed data
compress_data <- function(data) {
# Use more memory-efficient data types
compressed_data <- data
for (col in names(data)) {
if (is.numeric(data[[col]])) {
# Use integer if possible
if (all(data[[col]] == as.integer(data[[col]]), na.rm = TRUE)) {
compressed_data[[col]] <- as.integer(data[[col]])
} else {
# Use single precision if possible
if (all(abs(data[[col]]) < 3.4e38, na.rm = TRUE)) {
compressed_data[[col]] <- as.single(data[[col]])
}
}
} else if (is.character(data[[col]])) {
# Convert to factor if many repeated values
if (length(unique(data[[col]])) < length(data[[col]]) * 0.5) {
compressed_data[[col]] <- as.factor(data[[col]])
}
}
}
return(compressed_data)
}
#' Enable lazy loading
#'
#' @param data Data frame
#' @return Lazy-loaded data
enable_lazy_loading <- function(data) {
library(dplyr)
# Convert to lazy data frame
lazy_data <- lazy_df(data)
return(lazy_data)
}
#' Enable chunk processing
#'
#' @param data Data frame
#' @param chunk_size Chunk size
#' @return Chunked data
enable_chunk_processing <- function(data, chunk_size = 1000) {
# Create chunk indices
n_rows <- nrow(data)
chunk_indices <- split(1:n_rows, ceiling(seq_along(1:n_rows) / chunk_size))
# Create chunked data structure
chunked_data <- list(
data = data,
chunk_indices = chunk_indices,
chunk_size = chunk_size
)
return(chunked_data)
}
Vectorization and Optimization¶
Vectorized Operations¶
# R/03-vectorization.R
#' Optimize code using vectorization
#'
#' @param code_expression Code expression to optimize
#' @param optimization_type Type of vectorization
#' @return Optimized code
optimize_with_vectorization <- function(code_expression, optimization_type) {
switch(optimization_type,
"apply_family" = optimize_with_apply_family(code_expression),
"vectorized_math" = optimize_with_vectorized_math(code_expression),
"matrix_operations" = optimize_with_matrix_operations(code_expression),
"string_operations" = optimize_with_string_operations(code_expression),
stop("Unsupported optimization type: ", optimization_type)
)
}
#' Optimize using apply family
#'
#' @param code_expression Code expression
#' @return Optimized code
optimize_with_apply_family <- function(code_expression) {
# Replace loops with apply functions
optimized_code <- substitute({
# Use lapply instead of for loops
result <- lapply(data, function(x) {
# Vectorized operation
x * 2
})
})
return(optimized_code)
}
#' Optimize using vectorized math
#'
#' @param code_expression Code expression
#' @return Optimized code
optimize_with_vectorized_math <- function(code_expression) {
# Replace element-wise operations with vectorized operations
optimized_code <- substitute({
# Vectorized mathematical operations
result <- sqrt(x^2 + y^2)
})
return(optimized_code)
}
#' Optimize using matrix operations
#'
#' @param code_expression Code expression
#' @return Optimized code
optimize_with_matrix_operations <- function(code_expression) {
# Use matrix operations instead of loops
optimized_code <- substitute({
# Matrix multiplication
result <- matrix1 %*% matrix2
})
return(optimized_code)
}
#' Optimize using string operations
#'
#' @param code_expression Code expression
#' @return Optimized code
optimize_with_string_operations <- function(code_expression) {
# Use vectorized string operations
optimized_code <- substitute({
# Vectorized string operations
result <- toupper(strings)
})
return(optimized_code)
}
Loop Optimization¶
# R/03-vectorization.R (continued)
#' Optimize loops for performance
#'
#' @param loop_code Loop code to optimize
#' @param optimization_type Type of loop optimization
#' @return Optimized loop code
optimize_loops <- function(loop_code, optimization_type) {
switch(optimization_type,
"preallocate" = preallocate_vectors(loop_code),
"vectorize" = vectorize_loop(loop_code),
"parallelize" = parallelize_loop(loop_code),
"unroll" = unroll_loop(loop_code),
stop("Unsupported optimization type: ", optimization_type)
)
}
#' Preallocate vectors in loops
#'
#' @param loop_code Loop code
#' @return Optimized loop code
preallocate_vectors <- function(loop_code) {
# Preallocate result vectors
optimized_code <- substitute({
# Preallocate result vector
result <- numeric(length(input))
# Loop with preallocated vector
for (i in seq_along(input)) {
result[i] <- input[i] * 2
}
})
return(optimized_code)
}
#' Vectorize loop
#'
#' @param loop_code Loop code
#' @return Vectorized code
vectorize_loop <- function(loop_code) {
# Replace loop with vectorized operation
optimized_code <- substitute({
# Vectorized operation
result <- input * 2
})
return(optimized_code)
}
#' Parallelize loop
#'
#' @param loop_code Loop code
#' @return Parallelized code
parallelize_loop <- function(loop_code) {
# Use parallel processing
optimized_code <- substitute({
library(parallel)
# Set up parallel processing
cores <- detectCores()
cl <- makeCluster(cores)
# Parallel loop
result <- parLapply(cl, input, function(x) {
x * 2
})
# Clean up
stopCluster(cl)
})
return(optimized_code)
}
#' Unroll loop
#'
#' @param loop_code Loop code
#' @return Unrolled loop code
unroll_loop <- function(loop_code) {
# Unroll small loops
optimized_code <- substitute({
# Unrolled loop
result[1] <- input[1] * 2
result[2] <- input[2] * 2
result[3] <- input[3] * 2
result[4] <- input[4] * 2
})
return(optimized_code)
}
Parallel Processing¶
Multi-core Processing¶
# R/04-parallel-processing.R
#' Optimize code using parallel processing
#'
#' @param code_expression Code expression to parallelize
#' @param parallel_type Type of parallel processing
#' @param parameters Parallel processing parameters
#' @return Parallelized code
optimize_with_parallel_processing <- function(code_expression, parallel_type, parameters = list()) {
switch(parallel_type,
"foreach" = parallelize_with_foreach(code_expression, parameters),
"parallel" = parallelize_with_parallel(code_expression, parameters),
"future" = parallelize_with_future(code_expression, parameters),
"mclapply" = parallelize_with_mclapply(code_expression, parameters),
stop("Unsupported parallel type: ", parallel_type)
)
}
#' Parallelize using foreach
#'
#' @param code_expression Code expression
#' @param parameters Parallel parameters
#' @return Parallelized code
parallelize_with_foreach <- function(code_expression, parameters) {
library(foreach)
library(doParallel)
# Set up parallel backend
cores <- parameters$cores %||% detectCores()
cl <- makeCluster(cores)
registerDoParallel(cl)
# Parallelized code
parallelized_code <- substitute({
result <- foreach(i = 1:n, .combine = c) %dopar% {
# Parallel computation
compute_function(data[i])
}
})
# Clean up
stopCluster(cl)
return(parallelized_code)
}
#' Parallelize using parallel package
#'
#' @param code_expression Code expression
#' @param parameters Parallel parameters
#' @return Parallelized code
parallelize_with_parallel <- function(code_expression, parameters) {
library(parallel)
# Set up parallel processing
cores <- parameters$cores %||% detectCores()
cl <- makeCluster(cores)
# Parallelized code
parallelized_code <- substitute({
result <- parLapply(cl, data, function(x) {
# Parallel computation
compute_function(x)
})
})
# Clean up
stopCluster(cl)
return(parallelized_code)
}
#' Parallelize using future
#'
#' @param code_expression Code expression
#' @param parameters Parallel parameters
#' @return Parallelized code
parallelize_with_future <- function(code_expression, parameters) {
library(future)
library(future.apply)
# Set up parallel processing
plan(multisession, workers = parameters$cores %||% detectCores())
# Parallelized code
parallelized_code <- substitute({
result <- future_lapply(data, function(x) {
# Parallel computation
compute_function(x)
})
})
return(parallelized_code)
}
#' Parallelize using mclapply
#'
#' @param code_expression Code expression
#' @param parameters Parallel parameters
#' @return Parallelized code
parallelize_with_mclapply <- function(code_expression, parameters) {
library(parallel)
# Parallelized code
parallelized_code <- substitute({
result <- mclapply(data, function(x) {
# Parallel computation
compute_function(x)
}, mc.cores = parameters$cores %||% detectCores())
})
return(parallelized_code)
}
Distributed Computing¶
# R/04-parallel-processing.R (continued)
#' Optimize code using distributed computing
#'
#' @param code_expression Code expression to distribute
#' @param distributed_type Type of distributed computing
#' @param parameters Distributed computing parameters
#' @return Distributed code
optimize_with_distributed_computing <- function(code_expression, distributed_type, parameters = list()) {
switch(distributed_type,
"spark" = distribute_with_spark(code_expression, parameters),
"hadoop" = distribute_with_hadoop(code_expression, parameters),
"mpi" = distribute_with_mpi(code_expression, parameters),
stop("Unsupported distributed type: ", distributed_type)
)
}
#' Distribute using Spark
#'
#' @param code_expression Code expression
#' @param parameters Spark parameters
#' @return Distributed code
distribute_with_spark <- function(code_expression, parameters) {
library(sparklyr)
# Connect to Spark
sc <- spark_connect(master = parameters$master %||% "local")
# Distributed code
distributed_code <- substitute({
# Copy data to Spark
spark_data <- copy_to(sc, data, "spark_data")
# Perform distributed computation
result <- spark_data %>%
spark_apply(function(x) {
# Distributed computation
compute_function(x)
})
})
# Clean up
spark_disconnect(sc)
return(distributed_code)
}
#' Distribute using Hadoop
#'
#' @param code_expression Code expression
#' @param parameters Hadoop parameters
#' @return Distributed code
distribute_with_hadoop <- function(code_expression, parameters) {
library(rhdfs)
library(rmr2)
# Set up Hadoop
hdfs.init()
# Distributed code
distributed_code <- substitute({
# Map function
map_function <- function(k, v) {
# Map computation
keyval(k, compute_function(v))
}
# Reduce function
reduce_function <- function(k, v) {
# Reduce computation
keyval(k, sum(v))
}
# Run MapReduce job
result <- mapreduce(
input = parameters$input_path,
output = parameters$output_path,
map = map_function,
reduce = reduce_function
)
})
return(distributed_code)
}
Memory Management¶
Garbage Collection Optimization¶
# R/05-memory-management.R
#' Optimize garbage collection
#'
#' @param code_expression Code expression to optimize
#' @param gc_type Type of garbage collection
#' @return Optimized code
optimize_garbage_collection <- function(code_expression, gc_type = "auto") {
switch(gc_type,
"auto" = optimize_auto_gc(code_expression),
"manual" = optimize_manual_gc(code_expression),
"tuning" = optimize_gc_tuning(code_expression),
stop("Unsupported GC type: ", gc_type)
)
}
#' Optimize automatic garbage collection
#'
#' @param code_expression Code expression
#' @return Optimized code
optimize_auto_gc <- function(code_expression) {
# Let R handle garbage collection automatically
optimized_code <- substitute({
# Code with automatic GC
result <- compute_function(data)
})
return(optimized_code)
}
#' Optimize manual garbage collection
#'
#' @param code_expression Code expression
#' @return Optimized code
optimize_manual_gc <- function(code_expression) {
# Manually control garbage collection
optimized_code <- substitute({
# Disable automatic GC
gc(verbose = FALSE)
# Code execution
result <- compute_function(data)
# Force garbage collection
gc(verbose = FALSE)
})
return(optimized_code)
}
#' Optimize GC tuning
#'
#' @param code_expression Code expression
#' @return Optimized code
optimize_gc_tuning <- function(code_expression) {
# Tune garbage collection parameters
optimized_code <- substitute({
# Set GC tuning parameters
gc(verbose = FALSE, reset = TRUE)
# Code execution
result <- compute_function(data)
# Tune GC parameters
gc(verbose = FALSE, reset = FALSE)
})
return(optimized_code)
}
Memory Profiling¶
# R/05-memory-management.R (continued)
#' Profile memory usage
#'
#' @param code_expression Code expression to profile
#' @return Memory profiling results
profile_memory_usage <- function(code_expression) {
library(pryr)
# Get memory before
memory_before <- mem_used()
# Execute code
result <- eval(code_expression)
# Get memory after
memory_after <- mem_used()
# Calculate memory usage
memory_usage <- memory_after - memory_before
# Get object sizes
object_sizes <- sapply(ls(), function(x) object_size(get(x)))
return(list(
memory_before = memory_before,
memory_after = memory_after,
memory_usage = memory_usage,
object_sizes = object_sizes,
result = result
))
}
#' Optimize memory usage
#'
#' @param code_expression Code expression to optimize
#' @param optimization_type Type of memory optimization
#' @return Optimized code
optimize_memory_usage <- function(code_expression, optimization_type) {
switch(optimization_type,
"remove_objects" = remove_unused_objects(code_expression),
"compress_objects" = compress_objects(code_expression),
"lazy_evaluation" = enable_lazy_evaluation(code_expression),
stop("Unsupported optimization type: ", optimization_type)
)
}
#' Remove unused objects
#'
#' @param code_expression Code expression
#' @return Optimized code
remove_unused_objects <- function(code_expression) {
# Remove unused objects
optimized_code <- substitute({
# Code execution
result <- compute_function(data)
# Remove unused objects
rm(unused_object1, unused_object2)
# Force garbage collection
gc(verbose = FALSE)
})
return(optimized_code)
}
#' Compress objects
#'
#' @param code_expression Code expression
#' @return Optimized code
compress_objects <- function(code_expression) {
# Compress objects to save memory
optimized_code <- substitute({
# Compress data
compressed_data <- compress(data)
# Code execution
result <- compute_function(compressed_data)
# Decompress result
decompressed_result <- decompress(result)
})
return(optimized_code)
}
#' Enable lazy evaluation
#'
#' @param code_expression Code expression
#' @return Optimized code
enable_lazy_evaluation <- function(code_expression) {
# Use lazy evaluation to save memory
optimized_code <- substitute({
# Create lazy data structure
lazy_data <- lazy_df(data)
# Code execution with lazy evaluation
result <- compute_function(lazy_data)
})
return(optimized_code)
}
TL;DR Runbook¶
Quick Start¶
# 1. Profile code
prof_results <- profile_code(compute_function(data), "line")
# 2. Benchmark implementations
bench_results <- benchmark_implementations(list(impl1, impl2), data)
# 3. Optimize data structures
optimized_data <- optimize_data_structures(data, "data_table")
# 4. Vectorize operations
vectorized_code <- optimize_with_vectorization(loop_code, "apply_family")
# 5. Parallelize processing
parallel_code <- optimize_with_parallel_processing(code, "foreach", list(cores = 4))
# 6. Optimize memory usage
memory_optimized <- optimize_memory_usage(code, "remove_objects")
Essential Patterns¶
# Complete performance optimization pipeline
optimize_performance <- function(code, data, optimization_config) {
# Profile current performance
prof_results <- profile_code(code)
# Benchmark different implementations
bench_results <- benchmark_implementations(optimization_config$implementations, data)
# Optimize data structures
optimized_data <- optimize_data_structures(data, optimization_config$data_structure)
# Vectorize operations
vectorized_code <- optimize_with_vectorization(code, optimization_config$vectorization)
# Parallelize if beneficial
if (optimization_config$parallelize) {
parallel_code <- optimize_with_parallel_processing(vectorized_code,
optimization_config$parallel_type,
optimization_config$parallel_params)
}
# Optimize memory usage
memory_optimized <- optimize_memory_usage(parallel_code, optimization_config$memory_optimization)
return(memory_optimized)
}
This guide provides the complete machinery for optimizing R code performance. Each pattern includes implementation examples, profiling strategies, and real-world usage patterns for enterprise deployment.