| name | r-performance |
| description | R performance best practices including profiling, benchmarking, vctrs, and optimization strategies. Use when optimizing R code. |
R Performance Best Practices
Profiling, benchmarking, and optimization strategies for R code
Performance Tool Selection Guide
When to Use Each Performance Tool
Profiling Tools Decision Matrix
| Tool | Use When | Don't Use When | What It Shows |
|---|
profvis | Complex code, unknown bottlenecks | Simple functions, known issues | Time per line, call stack |
bench::mark() | Comparing alternatives | Single approach | Relative performance, memory |
system.time() | Quick checks | Detailed analysis | Total runtime only |
Rprof() | Base R only environments | When profvis available | Raw profiling data |
Step-by-Step Performance Workflow
library(profvis)
profvis({
})
library(bench)
bench::mark(
current = current_approach(data),
vectorized = vectorized_approach(data),
parallel = map(data, in_parallel(func))
)
When Each Tool Helps vs Hurts
Parallel Processing (in_parallel())
expensive_func <- function(x) Sys.sleep(0.1)
fast_func <- function(x) x^2
map(1:100, in_parallel(expensive_func))
map(1:100, in_parallel(fast_func))
vctrs Backend Tools
simple_combine <- function(x, y) c(x, y)
robust_combine <- function(x, y) vec_c(x, y)
Data Backend Selection
Profiling Best Practices
profvis({
real_data |> your_analysis()
})
bench::mark(
your_function(data),
min_iterations = 10,
max_iterations = 100
)
bench::mark(
approach1 = method1(data),
approach2 = method2(data),
check = FALSE,
filter_gc = FALSE
)
Performance Anti-Patterns to Avoid
Backend Tools for Performance
- Consider lower-level tools when speed is critical
- Use vctrs, rlang backends when appropriate
- Profile to identify true bottlenecks
When to Use vctrs
Core Benefits
- Type stability - Predictable output types regardless of input values
- Size stability - Predictable output sizes from input sizes
- Consistent coercion rules - Single set of rules applied everywhere
- Robust class design - Proper S3 vector infrastructure
Use vctrs when
Building Custom Vector Classes
new_percent <- function(x = double()) {
vec_assert(x, double())
new_vctr(x, class = "pkg_percent")
}
Type-Stable Functions in Packages
my_function <- function(x, y) {
vec_cast(result, double())
}
sapply(x, function(i) if(condition) 1L else 1.0)
Consistent Coercion/Casting
vec_cast(x, double())
vec_ptype_common(x, y, z)
c(factor("a"), "b")
Size/Length Stability
vec_c(x, y)
vec_rbind(df1, df2)
c(env_object, function_object)
vctrs vs Base R Decision Matrix
| Use Case | Base R | vctrs | When to Choose vctrs |
|---|
| Simple combining | c() | vec_c() | Need type stability, consistent rules |
| Custom classes | S3 manually | new_vctr() | Want data frame compatibility, subsetting |
| Type conversion | as.*() | vec_cast() | Need explicit, safe casting |
| Finding common type | Not available | vec_ptype_common() | Combining heterogeneous inputs |
| Size operations | length() | vec_size() | Working with non-vector objects |
Implementation Patterns
Basic Vector Class
new_percent <- function(x = double()) {
vec_assert(x, double())
new_vctr(x, class = "pkg_percent")
}
percent <- function(x = double()) {
x <- vec_cast(x, double())
new_percent(x)
}
format.pkg_percent <- function(x, ...) {
paste0(vec_data(x) * 100, "%")
}
Coercion Methods
vec_ptype2.pkg_percent.pkg_percent <- function(x, y, ...) {
new_percent()
}
vec_ptype2.pkg_percent.double <- function(x, y, ...) double()
vec_ptype2.double.pkg_percent <- function(x, y, ...) double()
vec_cast.pkg_percent.double <- function(x, to, ...) {
new_percent(x)
}
vec_cast.double.pkg_percent <- function(x, to, ...) {
vec_data(x)
}
Performance Considerations
When vctrs Adds Overhead
- Simple operations -
vec_c(1, 2) vs c(1, 2) for basic atomic vectors
- One-off scripts - Type safety less critical than speed
- Small vectors - Overhead may outweigh benefits
When vctrs Improves Performance
- Package functions - Type stability prevents expensive re-computation
- Complex classes - Consistent behavior reduces debugging
- Data frame operations - Robust column type handling
- Repeated operations - Predictable types enable optimization
Package Development Guidelines
Exports and Dependencies
Imports: vctrs
importFrom(vctrs, vec_assert, new_vctr, vec_cast, vec_ptype_common)
import(vctrs)
Testing vctrs Classes
test_that("my_function is type stable", {
expect_equal(vec_ptype(my_function(1:3)), vec_ptype(double()))
expect_equal(vec_ptype(my_function(integer())), vec_ptype(double()))
})
test_that("coercion works", {
expect_equal(vec_ptype_common(new_percent(), 1.0), double())
expect_error(vec_ptype_common(new_percent(), "a"
Don't Use vctrs When
- Simple one-off analyses - Base R is sufficient
- No custom classes needed - Standard types work fine
- Performance critical + simple operations - Base R may be faster
- External API constraints - Must return base R types
The key insight: vctrs is most valuable in package development where type safety, consistency, and extensibility matter more than raw speed for simple operations.
Performance Migrations
for loops for parallelizable work -> map(data, in_parallel(f))
Manual type checking -> vec_assert() / vec_cast()
Inconsistent coercion -> vec_ptype_common() / vec_c()