| name | r-performance |
| description | R performance best practices including profiling, benchmarking, vctrs, and optimization strategies. Use when mentions "profiling", "profvis", "benchmark", "bench::mark", "slow code", "código lento", "lento", "slow", "optimize R", "otimizar R", "otimizar código", "otimizar", "optimize", "optimize code", "vectorization", "vectorizar", "vectorize", "performance", "memory usage", "bottleneck", "gargalo", "speed up", "acelerar", "parallel processing", "Rcpp", "system.time", or optimizing R code performance. ONLY R - do NOT activate for Python, C++, JavaScript optimization. |
| version | 1.1.0 |
| user-invocable | false |
| allowed-tools | Read, Grep, Glob, Bash(Rscript -e *) |
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()