r-performance について
R 開発者向けに、プロファイリング、ベンチマーク、最適化の選択肢を解説するスキルです。並列処理やデータバックエンド、vctrs を扱い、変更前にボトルネックを測定する方針を示します。
主な機能
- 01プロファイリング・ベンチマークツールの比較
- 02用途に応じたデータバックエンドの選択
- 03並列処理が有効な場面の説明
- 04型安定な vctrs 操作の案内
- 05パフォーマンス上のアンチパターンの提示
活用シーン
- 01R コードの遅い箇所の特定
- 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 対応