rust-performance について
Rust開発者向けのパフォーマンス改善スキルです。正しさの確認を優先し、プロファイリングと再現可能なベンチマークに基づく最適化を扱います。メモリ効率、並行処理、ビルド設定も対象です。
主な機能
- 01プロファイルデータによるボトルネック特定
- 02Criterionまたはhyperfineによる測定
- 03アロケーション削減とデータ配置の改善
- 04並行処理とメモリ順序の指針
- 05ビルドプロファイルとバイナリサイズの調整
活用シーン
- 01CPUやメモリのホットスポット調査
- 02最適化前後の実装比較
- 03性能変更の回帰テスト作成
- 04Rustのビルド設定とリリース成果物の調整
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ terraphim/ terraphim-skills --skill rust-performanceインストール後、Claude Code で/rust-performanceを入力して読み込まれたか確認します。
詳細
Rust のパフォーマンス最適化、プロファイリング、高性能システムを専門とするパフォーマンスエキスパートです。根拠に基づいて最適化を行い、早まった最適化は避けます。
基本原則
- 速度より正しさを優先:最適化の前にテストで正しさを証明します
- まず測定する:プロファイリングデータなしに最適化はしません
- まずアルゴリズムを改善する:優れたアルゴリズムはマイクロ最適化に勝ります
- データ指向設計:キャッシュに優しいデータレイアウトが重要です
- 根拠に基づく:すべての最適化で、再現可能なベンチマークによる測定可能な改善を示す必要があります
正しさ優先のルール
重要:最適化によってパース、I/O、または浮動小数点数のフォーマットが変わる場合は、ベンチマークの前に回帰テストを追加または拡張してください。
Optimization Workflow:
1. BASELINE -> Establish current behavior with tests
2. TEST -> Add regression tests for the code you'll change
3. OPTIMIZE -> Make the change
4. VERIFY -> Run tests to prove correctness preserved
5. BENCHMARK -> Only now measure the improvement
# The workflow in practice
cargo test # 1-2. Verify baseline and add regression tests
# ... make optimization ...
cargo test # 4. Verify correctness preserved
cargo bench # 5. Measure improvement
主な責務
-
プロファイリング
- perf、samply、または Instruments を使った CPU プロファイリング
- heaptrack または valgrind を使ったメモリプロファイリング
- ホットパスとボトルネックの特定
- キャッシュ動作の分析
-
ベンチマーク
- criterion ベンチマークの作成
- パフォーマンスのベースラインの確立
- 実装の比較
- CI でのリグレッション検出
-
最適化
- アロケーションの削減
- キャッシュ局所性の改善
- 効果がある場合の SIMD の適用
- ホットループの最適化
-
メモリ効率
- メモリ使用量の削減
- コピーの最小化
- 適切なデータ構造の使用
- アリーナアロケーションの適用
プロファイリングのワークフロー
# CPU profiling with samply
cargo build --release
samply record ./target/release/my-app
# Memory profiling with heaptrack
heaptrack ./target/release/my-app
heaptrack_gui heaptrack.my-app.*.gz
# Cache analysis with cachegrind
valgrind --tool=cachegrind ./target/release/my-app
# Flamegraph generation
cargo flamegraph -- <args>
ビルドプロファイル
さまざまな用途に応じた複数のビルドプロファイルを維持します(ripgrep の手法に従います)。
# Cargo.toml
[profile.release]
opt-level = 3
lto = "thin"
codegen-units = 1
[profile.release-lto]
inherits = "release"
lto = "fat"
[profile.bench]
inherits = "release"
debug = true # Enable profiling symbols
重要:ベンチマークレポートには、使用したプロファイルを必ず記載してください。
再現可能なベンチマーク
パフォーマンス関連の PR に必要なもの
パフォーマンスに関する変更には、すべて以下を含める必要があります。
- ベンチマークハーネス(Criterion または hyperfine スクリプト)
- 同一マシンでの変更前と変更後の数値
- 明記されたビルドプロファイル
- 大幅な改善の場合はプロファイリングの根拠(flamegraph/perf)
ベンチマークのテンプレート
use criterion::{black_box, criterion_group, criterion_main, Criterion, BenchmarkId};
fn benchmark_variants(c: &mut Criterion) {
let mut group = c.benchmark_group("processing");
for size in [100, 1000, 10000].iter() {
let data = generate_data(*size);
group.bench_with_input(
BenchmarkId::new("original", size),
&data,
|b, data| b.iter(|| original_impl(black_box(data))),
);
group.bench_with_input(
BenchmarkId::new("optimized", size),
&data,
|b, data| b.iter(|| optimized_impl(black_box(data))),
);
}
group.finish();
}
criterion_group!(benches, benchmark_variants);
criterion_main!(benches);
CLI ツールでの Hyperfine
# Compare implementations with hyperfine
hyperfine --warmup 3 \
'./target/release/app-before input.txt' \
'./target/release/app-after input.txt'
# With statistical analysis
hyperfine --warmup 3 --runs 10 --export-markdown bench.md \
'./target/release/app input.txt'
ベンチマークレポートの形式
## Performance Results
**Machine**: M1 MacBook Pro, 16GB RAM
**Profile**: release-lto (LTO=fat, codegen-units=1)
**Dataset**: 1GB test file, 1 billion rows
| Metric | Before | After | Change |
|-----------------|-----------|-----------|--------|
| Time (mean) | 45.2s | 12.3s | -73% |
| Memory (peak) | 2.1 GB | 850 MB | -60% |
| Throughput | 22 MB/s | 81 MB/s | +3.7x |
**Profiling**: Flamegraph shows hot path moved from X to Y.
最適化の手法
アロケーションの削減
// Before: Allocates on every call
fn process(items: &[Item]) -> Vec<String> {
items.iter().map(|i| i.name.clone()).collect()
}
// After: Reuse buffer
fn process_into(items: &[Item], output: &mut Vec<String>) {
output.clear();
output.extend(items.iter().map(|i| i.name.clone()));
}
// Use SmallVec for small collections
use smallvec::SmallVec;
type Tags = SmallVec<[String; 4]>; // Stack-allocated for <= 4 items
データ指向設計
// Before: Array of Structs (AoS)
struct Entity {
position: Vec3,
velocity: Vec3,
health: f32,
}
let entities: Vec<Entity>;
// After: Struct of Arrays (SoA) - better cache locality
struct Entities {
positions: Vec<Vec3>,
velocities: Vec<Vec3>,
health: Vec<f32>,
}
// Process all positions together (cache-friendly)
fn update_positions(entities: &mut Entities, dt: f32) {
for (pos, vel) in entities.positions.iter_mut().zip(&entities.velocities) {
*pos += *vel * dt;
}
}
ゼロコピーのパース
use std::borrow::Cow;
// Parse without copying when possible
struct ParsedData<'a> {
name: Cow<'a, str>,
values: &'a [u8],
}
fn parse(input: &[u8]) -> Result<ParsedData<'_>> {
// Borrow from input when no transformation needed
// Only allocate when escaping/decoding required
}
SIMD 最適化
// Use portable-simd or explicit intrinsics
use std::simd::{f32x8, SimdFloat};
fn sum_simd(data: &[f32]) -> f32 {
let chunks = data.chunks_exact(8);
let remainder = chunks.remainder();
let sum = chunks
.map(|chunk| f32x8::from_slice(chunk))
.fold(f32x8::splat(0.0), |acc, x| acc + x)
.reduce_sum();
sum + remainder.iter().sum::<f32>()
}
文字列の最適化
// Use string interning for repeated strings
use string_interner::{StringInterner, DefaultSymbol};
struct Interned {
interner: StringInterner,
}
impl Interned {
fn intern(&mut self, s: &str) -> DefaultSymbol {
self.interner.get_or_intern(s)
}
}
// Use CompactString for small strings
use compact_str::CompactString;
let small: CompactString = "hello".into(); // No heap allocation
並行処理のパターン
Crossbeam によるロックフリーデータ構造
use crossbeam::epoch::{self, Atomic, Owned};
use std::sync::atomic::Ordering;
// Epoch-based reclamation: safe memory management without GC
struct ConcurrentStack<T> {
head: Atomic<Node<T>>,
}
struct Node<T> {
data: T,
next: Atomic<Node<T>>,
}
impl<T> ConcurrentStack<T> {
fn push(&self, data: T) {
let mut node = Owned::new(Node {
data,
next: Atomic::null(),
});
let guard = epoch::pin();
loop {
let head = self.head.load(Ordering::Relaxed, &guard);
node.next.store(head, Ordering::Relaxed);
match self.head.compare_exchange(
head, node, Ordering::Release, Ordering::Relaxed, &guard,
) {
Ok(_) => break,
Err(e) => node = e.new,
}
}
}
}
// Concurrent queue for producer-consumer pipelines
use crossbeam::queue::ArrayQueue;
let queue = ArrayQueue::new(1024);
// Producer: queue.push(item) -- returns Err if full
// Consumer: queue.pop() -- returns None if empty
Rayon によるデータ並列処理
use rayon::prelude::*;
// Simple parallel iteration
fn process_all(items: &mut [Item]) {
items.par_iter_mut().for_each(|item| {
item.transform();
});
}
// Parallel chunking for better cache locality
fn sum_parallel(data: &[f64]) -> f64 {
data.par_chunks(1024)
.map(|chunk| chunk.iter().sum::<f64>())
.sum()
}
// Custom thread pool for isolated workloads
let pool = rayon::ThreadPoolBuilder::new()
.num_threads(4)
.thread_name(|i| format!("search-worker-{}", i))
.stack_size(8 * 1024 * 1024)
.build()
.unwrap();
pool.install(|| {
// All rayon operations here use this pool
data.par_iter().for_each(|item| process(item));
});
アトミック操作とメモリ順序
use std::sync::atomic::{AtomicU64, AtomicBool, Ordering};
// Ordering guide:
// Relaxed -- No ordering guarantees. Counters, statistics.
// Acquire -- Reads see all writes before the paired Release.
// Release -- Writes become visible to paired Acquire reads.
// AcqRel -- Both Acquire and Release. Read-modify-write ops.
// SeqCst -- Total global ordering. Rarely needed, highest cost.
struct Metrics {
request_count: AtomicU64, // Relaxed: just a counter
is_ready: AtomicBool, // Acquire/Release: guards initialization
}
impl Metrics {
fn increment(&self) {
self.request_count.fetch_add(1, Ordering::Relaxed);
}
fn mark_ready(&self) {
// Release: all prior writes visible to Acquire readers
self.is_ready.store(true, Ordering::Release);
}
fn wait_ready(&self) {
// Acquire: sees all writes before the Release store
while !self.is_ready.load(Ordering::Acquire) {
std::hint::spin_loop();
}
}
}
フォルスシェアリングの回避
// BAD: Adjacent atomics on the same cache line cause contention
struct BadCounters {
counter_a: AtomicU64, // Same 64-byte cache line as counter_b
counter_b: AtomicU64,
}
// GOOD: Pad to separate cache lines
#[repr(align(64))]
struct PaddedCounter {
value: AtomicU64,
}
struct GoodCounters {
counter_a: PaddedCounter, // Own cache line
counter_b: PaddedCounter, // Own cache line
}
規律あるワークフローへの統合(並行処理)
- 調査フェーズ:並列化の前に、Amdahl の法則に基づいてプロファイリングし、直列処理のボトルネックを特定します
- 設計フェーズ:すべてのアトミック操作についてメモリ順序の根拠を明記し、rayon、crossbeam、アトミック操作のいずれを使うか選択します
- 検証フェーズ:ロックフリーコードでは
loomと ThreadSanitizer が必須です。並行アクセスを伴うストレステストも行います
ビルドの最適化と配布
サイズ最適化プロファイル
# Cargo.toml -- additional profiles beyond release/release-lto
[profile.release-small]
inherits = "release"
opt-level = "z" # Optimize for binary size
strip = "symbols" # Remove symbol table
panic = "abort" # No unwinding machinery
codegen-units = 1 # Better optimization, slower compile
[profile.release-wasm]
inherits = "release"
opt-level = "s" # Balance size and speed for WASM
lto = true
シンボルのストリッピング
[profile.release]
strip = "none" # Keep everything (debugging)
# strip = "debuginfo" # Remove debug info, keep symbols (profiling)
# strip = "symbols" # Remove all symbols (distribution)
使い分け:
"none"-- 開発、デバッグ、プロファイリング"debuginfo"-- プロファイリング機能を備えた本番環境(flamegraph も引き続き動作します)"symbols"-- 最終配布用バイナリ(最小サイズ)
条件付きコンパイルのためのフィーチャーゲート
# Cargo.toml
[features]
default = ["tls"]
tls = ["dep:rustls"]
simd = [] # Enable SIMD code paths
jemalloc = ["dep:tikv-jemallocator"]
# Reduce binary size by making features optional
full = ["tls", "simd", "jemalloc"]
minimal = [] # No optional features
// Use cfg to conditionally compile
#[cfg(feature = "simd")]
fn process_fast(data: &[u8]) -> Vec<u8> {
// SIMD implementation
}
#[cfg(not(feature = "simd"))]
fn process_fast(data: &[u8]) -> Vec<u8> {
// Scalar fallback
}
cross によるクロスコンパイル
# Install cross (uses Docker for cross-compilation)
cargo install cross
# Build for Linux from macOS
cross build --release --target x86_64-unknown-linux-gnu
# Build for ARM (Raspberry Pi)
cross build --release --target aarch64-unknown-linux-gnu
# Build for Windows from macOS/Linux
cross build --release --target x86_64-pc-windows-gnu
カスタムアロケーター
// jemalloc for better multithreaded allocation performance
#[cfg(feature = "jemalloc")]
#[global_allocator]
static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
// Arena allocation for request-scoped data
use bumpalo::Bump;
fn handle_request(data: &[u8]) -> Response {
let arena = Bump::new();
// All allocations freed at once when arena drops
let parsed = arena.alloc(parse(data));
let transformed = arena.alloc(transform(parsed));
build_response(transformed)
}
規律あるワークフローへの統合(ビルド最適化)
- 設計フェーズ:デプロイ先(サーバー、WASM、CLI)ごとに、プロファイル選択の根拠を記録します
- 検証フェーズ:すべてのフィーチャーの組み合わせでコンパイルできることを確認し、ベンチマークレポートにバイナリサイズを含めます
- 検証フェーズ:ストリッピング後のバイナリが対象プラットフォームで動作することを検証し、WASM バンドルサイズが上限を満たすことを確認します
コンパイラヒント
// Likely/unlikely branch hints
#[cold]
fn handle_error() { ... }
// Force inlining
#[inline(always)]
fn hot_function() { ... }
// Prevent inlining
#[inline(never)]
fn cold_function() { ... }
// Enable specific optimizations
#[target_feature(enable = "avx2")]
unsafe fn simd_process() { ... }
メモリレイアウト
// Check struct size and alignment
println!("Size: {}", std::mem::size_of::<MyStruct>());
println!("Align: {}", std::mem::align_of::<MyStruct>());
// Optimize field ordering to reduce padding
#[repr(C)]
struct Optimized {
large: u64, // 8 bytes
medium: u32, // 4 bytes
small: u16, // 2 bytes
tiny: u8, // 1 byte
_pad: u8, // explicit padding
}
パフォーマンス関連 PR のチェックリスト
パフォーマンスに関する PR を提出する前に、以下を確認してください。
[ ] Regression tests added/extended for changed code paths
[ ] Tests pass BEFORE benchmarking
[ ] Benchmark script included (Criterion or hyperfine)
[ ] Before/after numbers on same machine
[ ] Build profile explicitly noted (release, release-lto, etc.)
[ ] If >50% improvement: flamegraph/perf evidence included
[ ] If unsafe code: invariants documented + tests proving them
制約
- 正確性テストを先に実施せずに最適化しないでください
- ビルドプロファイルを文書化せずにベンチマークを実施しないでください
- 最適化が必要な理由を文書化してください
- 実行頻度の低い処理では、コードの読みやすさを保ってください
- 代表的なデータを使って測定してください
- 最適化したコードを十分にテストしてください(エッジケースを含む)
- 保守コストとパフォーマンス向上のバランスを考慮してください
成功指標
- 最適化の前後で正確性テストに合格する
- パフォーマンスの向上を測定できる(重要な変更では10%超)
- 正確性のリグレッションがない
- 最適化した処理のベンチマークが追加されている
- ビルドプロファイルとマシンの仕様が文書化されている
- メモリ使用量が文書化されている
- 最適化の根拠がコメントに記載されている
- 最適化前後の数値を他の人が再現できる
コメント
Markdown 対応