Skip to Content
Course content

80: Profiling R Code with profvis

Click on the "Edit" button in the top corner of the screen to edit your slide content.

We've all been there: you write a piece of code that works perfectly on your test set of 100 rows, but the moment you throw it at the real production data, it crawls. The temptation is to start guessing. You might think, "Maybe this lapply is slow," or "I bet this regex is the bottleneck." But guessing is a waste of your time. I prefer to let the computer tell me exactly where it's struggling.

That's where profvis comes in. It's a wrapper around R's built-in profiling tools that gives us a visual, interactive breakdown of where every millisecond is being spent. Let's look at a scenario I ran into recently.

Creating a function that feels sluggish

Suppose I'm writing a function to simulate a random walk and calculate the running maximum at every step. I'll write this the "intuitive" way—the way many of us do when we first start with R—by growing a vector inside a loop.

library(profvis)

simulate_max_walk <- function(n = 10000) {
  walk <- numeric(1)
  max_vals <- numeric(1)
  
  current_val <- 0
  current_max <- 0
  
  for (i in 1:n) {
    current_val <- current_val + rnorm(1)
    if (current_val > current_max) {
      current_max <- current_val
    }
    # I'm appending the value to the vector here
    max_vals <- c(max_vals, current_max)
  }
  return(max_vals)
}

If you run this with n = 10000, it's fast enough that you might not notice. But if you bump that to 100,000, you'll feel the lag. Instead of just staring at the screen, let's profile it.

Putting the code under the microscope

To use profvis, you wrap the code you want to analyze inside the profvis() function. It'll open an interactive window in your RStudio viewer.

profvis({
  simulate_max_walk(100000)
})

When the output loads, you'll see a flame graph at the top and a table below. I'm looking specifically at the "Time" column. In my first run, I noticed something strange. A massive chunk of the time wasn't being spent on rnorm() or the if statement; it was being spent in c() and internal memory allocation functions.

The "Aha!" moment and the fix

Here is where I made my mistake. I initially assumed the bottleneck was the mathematical operations inside the loop. But the profiler showed me that R was spending almost all its effort re-allocating memory for max_vals every single time the loop iterated. Because I used c(max_vals, current_max), R had to find a new, larger spot in memory and copy the entire existing vector into it over and over again. It's a classic R performance trap.

The fix is simple: pre-allocate the vector. I'll tell R exactly how much memory I need upfront.

simulate_max_walk_fast <- function(n = 10000) {
  # Pre-allocate the vector with the final size
  max_vals <- numeric(n)
  
  current_val <- 0
  current_max <- 0
  
  for (i in 1:n) {
    current_val <- current_val + rnorm(1)
    if (current_val > current_max) {
      current_max <- current_val
    }
    # Assign to a specific index instead of appending
    max_vals[i] <- current_max
  }
  return(max_vals)
}

If you run this through profvis again, you'll see the time spent in c() vanish. The "hot spot" shifts to rnorm(), which is where the actual work is happening. You've successfully moved the bottleneck from memory management to actual computation.




📋 Practical Task

Optimizing a Heavy Data Aggregator

You have been given a function that processes a large set of simulated transaction data. The function is currently far too slow for the production dataset. Your goal is to use profvis to identify the bottleneck and fix it.

library(profvis)

# The slow function
process_transactions <- function(n = 50000) {
  data <- data.frame(
    id = 1:n,
    amount = runif(n, 1, 100),
    category = sample(letters[1:5], n, replace = TRUE)
  )
  
  results <- list()
  unique_cats <- unique(data$category)
  
  for (cat in unique_cats) {
    # Intentional bottleneck: filtering the whole dataframe inside a loop
    cat_data <- data[data$category == cat, ]
    results[[cat]] <- mean(cat_data$amount)
  }
  
  return(results)
}

# Your task:
# 1. Run the function inside profvis()
# 2. Identify why the loop is inefficient
# 3. Rewrite the function to be more efficient (Hint: Think about split() or aggregate())
# 4. Profile the new version to verify the time spent in the loop has decreased
Rating
0 0

There are no comments for now.

to be the first to leave a comment.