mcpField
R
r-performance

r-performance

github.com/ab604开发21068 次安装6 周前更新
代码仓库

关于 r-performance

为 R 开发者提供性能分析、基准测试和优化指导,涵盖工具选择、并行处理、数据后端及 vctrs。重点是先测量瓶颈,再选择合适的优化方式。

主要功能

  1. 01比较性能分析与基准测试工具
  2. 02按场景选择数据后端
  3. 03说明并行处理的适用情形
  4. 04指导 vctrs 类型稳定操作
  5. 05列出性能反模式

适用场景

  1. 01定位 R 代码中的耗时部分
  2. 02比较不同实现的性能和内存
  3. 03为大型数据操作选择后端
  4. 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 Rvctrs选择 vctrs 的时机
简单合并c()vec_c()需要类型稳定性和一致的规则
自定义类手动实现 S3new_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