关于 r-performance
为 R 开发者提供性能分析、基准测试和优化指导,涵盖工具选择、并行处理、数据后端及 vctrs。重点是先测量瓶颈,再选择合适的优化方式。
主要功能
- 01比较性能分析与基准测试工具
- 02按场景选择数据后端
- 03说明并行处理的适用情形
- 04指导 vctrs 类型稳定操作
- 05列出性能反模式
适用场景
- 01定位 R 代码中的耗时部分
- 02比较不同实现的性能和内存
- 03为大型数据操作选择后端
- 04构建类型稳定的包函数
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ ab604/ claude-code-r-skills --skill r-performance安装后在 Claude Code 中输入/r-performance确认已加载。
详细介绍
R 性能最佳实践
R 代码的性能分析、基准测试和优化策略
性能工具选择指南
何时使用各类性能工具
性能分析工具决策矩阵
| 工具 | 适用场景 | 不适用场景 | 展示内容 |
|---|---|---|---|
profvis | 复杂代码、瓶颈未知 | 简单函数、问题已知 | 每行耗时、调用栈 |
bench::mark() | 比较不同方案 | 只有一种方案 | 相对性能、内存 |
system.time() | 快速检查 | 详细分析 | 仅总运行时间 |
Rprof() | 仅有 Base R 的环境 | 可使用 profvis 时 | 原始性能分析数据 |
分步性能工作流
# 1. Profile first - find the actual bottlenecks
library(profvis)
profvis({
# Your slow code here
})
# 2. Focus on the slowest parts (80/20 rule)
# Don't optimize until you know where time is spent
# 3. Benchmark alternatives for hot spots
library(bench)
bench::mark(
current = current_approach(data),
vectorized = vectorized_approach(data),
parallel = map(data, in_parallel(func))
)
# 4. Consider tool trade-offs based on bottleneck type
各工具何时有帮助、何时会适得其反
并行处理(in_parallel())
# Helps when:
# - CPU-intensive computations
# - Embarassingly parallel problems
# - Large datasets with independent operations
# - I/O bound operations (file reading, API calls)
# Hurts when:
# - Simple, fast operations (overhead > benefit)
# - Memory-intensive operations (may cause thrashing)
# - Operations requiring shared state
# - Small datasets
# Example decision point:
expensive_func <- function(x) Sys.sleep(0.1) # 100ms per call
fast_func <- function(x) x^2 # microseconds per call
# Good for parallel
map(1:100, in_parallel(expensive_func)) # ~10s -> ~2.5s on 4 cores
# Bad for parallel (overhead > benefit)
map(1:100, in_parallel(fast_func)) # 100us -> 50ms (500x slower!)
vctrs 后端工具
# Use vctrs when:
# - Type safety matters more than raw speed
# - Building reusable package functions
# - Complex coercion/combination logic
# - Consistent behavior across edge cases
# Avoid vctrs when:
# - One-off scripts where speed matters most
# - Simple operations where base R is sufficient
# - Memory is extremely constrained
# Decision point:
simple_combine <- function(x, y) c(x, y) # Fast, simple
robust_combine <- function(x, y) vec_c(x, y) # Safer, slight overhead
# Use simple for hot loops, robust for package APIs
数据后端选择
# Use data.table when:
# - Very large datasets (>1GB)
# - Complex grouping operations
# - Reference semantics desired
# - Maximum performance critical
# Use dplyr when:
# - Readability and maintainability priority
# - Complex joins and window functions
# - Team familiarity with tidyverse
# - Moderate sized data (<100MB)
# Use base R when:
# - No dependencies allowed
# - Simple operations
# - Teaching/learning contexts
性能分析最佳实践
# 1. Profile realistic data sizes
profvis({
# Use actual data size, not toy examples
real_data |> your_analysis()
})
# 2. Profile multiple runs for stability
bench::mark(
your_function(data),
min_iterations = 10, # Multiple runs
max_iterations = 100
)
# 3. Check memory usage too
bench::mark(
approach1 = method1(data),
approach2 = method2(data),
check = FALSE, # If outputs differ slightly
filter_gc = FALSE # Include GC time
)
# 4. Profile with realistic usage patterns
# Not just isolated function calls
应避免的性能反模式
# Don't optimize without measuring
# BAD: "This looks slow" -> immediately rewrite
# GOOD: Profile first, optimize bottlenecks
# Don't over-engineer for performance
# BAD: Complex optimizations for 1% gains
# GOOD: Focus on algorithmic improvements
# Don't assume - measure
# BAD: "for loops are always slow in R"
# GOOD: Benchmark your specific use case
# Don't ignore readability costs
# BAD: Unreadable code for minor speedups
# GOOD: Readable code with targeted optimizations
用于提升性能的后端工具
- 速度至关重要时,考虑使用更底层的工具
- 适用时使用 vctrs、rlang 后端
- 通过性能分析找出真正的瓶颈
# For packages - consider backend tools
# vctrs for type-stable vector operations
# rlang for metaprogramming
# data.table for large data operations
何时使用 vctrs
核心优势
- 类型稳定性 - 无论输入值如何,输出类型都可预测
- 大小稳定性 - 可根据输入大小预测输出大小
- 一致的强制转换规则 - 在任何地方都应用同一套规则
- 稳健的类设计 - 完备的 S3 向量基础设施
使用 vctrs 的场景
构建自定义向量类
# Good - vctrs-based vector class
new_percent <- function(x = double()) {
vec_assert(x, double())
new_vctr(x, class = "pkg_percent")
}
# Automatic data frame compatibility, subsetting, etc.
在包中编写类型稳定的函数
# Good - Guaranteed output type
my_function <- function(x, y) {
# Always returns double, regardless of input values
vec_cast(result, double())
}
# Avoid - Type depends on data
sapply(x, function(i) if(condition) 1L else 1.0)
一致的强制转换/类型转换
# Good - Explicit casting with clear rules
vec_cast(x, double()) # Clear intent, predictable behavior
# Good - Common type finding
vec_ptype_common(x, y, z) # Finds richest compatible type
# Avoid - Base R inconsistencies
c(factor("a"), "b") # Unpredictable behavior
大小/长度稳定性
# Good - Predictable sizing
vec_c(x, y) # size = vec_size(x) + vec_size(y)
vec_rbind(df1, df2) # size = sum of input sizes
# Avoid - Unpredictable sizing
c(env_object, function_object) # Unpredictable length
vctrs 与 Base R 决策矩阵
| 使用场景 | Base R | vctrs | 选择 vctrs 的时机 |
|---|---|---|---|
| 简单合并 | c() | vec_c() | 需要类型稳定性和一致的规则 |
| 自定义类 | 手动实现 S3 | new_vctr() | 需要兼容数据框及支持子集操作 |
| 类型转换 | as.*() | vec_cast() | 需要显式且安全的类型转换 |
| 查找公共类型 | 不可用 | vec_ptype_common() | 合并异构输入时 |
| 大小操作 | length() | vec_size() | 处理非向量对象时 |
实现模式
基本向量类
# Constructor (low-level)
new_percent <- function(x = double()) {
vec_assert(x, double())
new_vctr(x, class = "pkg_percent")
}
# Helper (user-facing)
percent <- function(x = double()) {
x <- vec_cast(x, double())
new_percent(x)
}
# Format method
format.pkg_percent <- function(x, ...) {
paste0(vec_data(x) * 100, "%")
}
强制转换方法
# Self-coercion
vec_ptype2.pkg_percent.pkg_percent <- function(x, y, ...) {
new_percent()
}
# With double
vec_ptype2.pkg_percent.double <- function(x, y, ...) double()
vec_ptype2.double.pkg_percent <- function(x, y, ...) double()
# Casting
vec_cast.pkg_percent.double <- function(x, to, ...) {
new_percent(x)
}
vec_cast.double.pkg_percent <- function(x, to, ...) {
vec_data(x)
}
性能注意事项
vctrs 会增加开销的场景
- 简单操作 - 对基本原子向量,比较
vec_c(1, 2)与c(1, 2) - 一次性脚本 - 与速度相比,类型安全没那么重要
- 小型向量 - 开销可能超过收益
vctrs 能提升性能的场景
- 包函数 - 类型稳定性可避免昂贵的重复计算
- 复杂类 - 一致的行为可减少调试工作
- 数据框操作 - 稳健地处理列类型
- 重复操作 - 可预测的类型有利于优化
包开发指南
导出和依赖项
# DESCRIPTION - Import specific functions
Imports: vctrs
# NAMESPACE - Import what you need
importFrom(vctrs, vec_assert, new_vctr, vec_cast, vec_ptype_common)
# Or if using extensively
import(vctrs)
测试 vctrs 类
# Test type stability
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 coercion
test_that("coercion works", {
expect_equal(vec_ptype_common(new_percent(), 1.0), double())
expect_error(vec_ptype_common(new_percent(), "a"))
})
不要使用 vctrs 的场景
- 简单的一次性分析 - Base R 已足够
- 无需自定义类 - 标准类型即可满足需求
- 对性能要求极高且操作简单 - Base R 可能更快
- 受外部 API 限制 - 必须返回 Base R 类型
关键在于:在包开发中,vctrs 最有价值,因为此时类型安全、一致性和可扩展性比简单操作的原始速度更重要。
性能迁移
# Old -> New performance patterns
for loops for parallelizable work -> map(data, in_parallel(f))
Manual type checking -> vec_assert() / vec_cast()
Inconsistent coercion -> vec_ptype_common() / vec_c()
评论
支持 Markdown