Skip to content

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.