用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/tomevault-io/skills-registry --skill rust-performance命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
| Use when this capability is needed.
> Use when this capability is needed.
Review architecture and API design for the vfs-s3 project. Use when the user mentions @architect, asks to review an issue's design, discuss module boundaries, API shape, or architectural decisions for vfs-s3. Also trigger when the user wants to create an ADR (Architecture Decision Record) or evaluate a technical approach for the project. Intended for dispatch from Codex automation or Claude routines; GitHub trigger phrase: @vfs-s3-bot please prepare design doc Use when this capability is needed.
基于 SOC 职业分类
正在显示 SKILL.md
| name | rust-performance |
| description | | Use when this capability is needed. |
Comprehensive guide to profiling, measuring, and optimizing Rust code. Based on "The Rust Performance Book" and real-world optimization patterns.
Always benchmark in release mode with optimizations:
# Cargo.toml
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
panic = "abort"
# For profiling (symbols but optimized)
[profile.release-with-debug]
inherits = "release"
debug = true
cargo build --release
cargo run --release
&str Instead of String// Allocates on every call
fn greet_slow(name: String) {
println!("Hello, {}!", name);
}
// Zero allocation
fn greet_fast(name: &str) {
println!("Hello, {}!", name);
}
// Reallocates as it grows
let mut v = Vec::new();
for i in 0..1000 {
v.push(i);
}
// Single allocation
let mut v = Vec::with_capacity(1000);
for i in 0..1000 {
v.push(i);
}
// Even better: use iterators
let v: Vec<_> = (0..1000).collect();
// Bad: clones unnecessarily
fn process(items: Vec<Item>) -> Vec<Result> {
items.iter()
.map(|item| item.clone()) // Don't clone if you don't need it
.map(|item| transform(item))
.collect()
}
// Good: take ownership or borrow
fn process(items: Vec<Item>) -> Vec<Result> {
items.into_iter() // Consume the Vec
.map(transform)
.collect()
}
Cow<str> for Maybe-Owned Stringsuse std::borrow::Cow;
fn normalize(input: &str) -> Cow<'_, str> {
if input.contains(' ') {
Cow::Owned(input.replace(' ', "_"))
} else {
Cow::Borrowed(input) // No allocation
}
}
collect() Strategically// Creates intermediate Vec
let sum: i32 = items.iter()
.map(|x| x * 2)
.collect::<Vec<_>>() // Unnecessary
.iter()
.sum();
// Direct iteration
let sum: i32 = items.iter()
.map(|x| x * 2)
.sum();
The gold standard for Rust benchmarks:
[dev-dependencies]
criterion = "0.5"
[[bench]]
name = "my_benchmark"
harness = false
// benches/my_benchmark.rs
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn fibonacci(n: u64) -> u64 {
match n {
0 | 1 => n,
_ => fibonacci(n - 1) + fibonacci(n - 2),
}
}
fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("fib 20", |b| {
b.iter(|| fibonacci(black_box(20)))
});
// Compare implementations
let mut group = c.benchmark_group("String Ops");
group.bench_function("clone", |b| {
b.iter(|| String::from("hello").clone())
});
group.bench_function("to_string", |b| {
b.iter(|| "hello".to_string())
});
group.finish();
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);
cargo bench
cargo bench -- "fib" # Run specific benchmarks
black_box() to prevent optimizationperf (Linux):
perf record -g --call-graph dwarf target/release/myapp
perf report
flamegraph:
cargo install flamegraph
cargo flamegraph --bin myapp
# Open flamegraph.svg in browser
samply (Cross-platform):
cargo install samply
samply record target/release/myapp
DHAT (Heap profiling):
[dependencies]
dhat = "0.3"
#[cfg(feature = "dhat")]
#[global_allocator]
static ALLOC: dhat::Alloc = dhat::Alloc;
fn main() {
#[cfg(feature = "dhat")]
let _profiler = dhat::Profiler::new_heap();
// Your code here
}
Heaptrack (Linux):
heaptrack target/release/myapp
heaptrack --analyze heaptrack.myapp.*.zst
// Track allocations with a custom allocator
use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicUsize, Ordering};
static ALLOCATED: AtomicUsize = AtomicUsize::new(0);
struct CountingAlloc;
unsafe impl GlobalAlloc for CountingAlloc {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
ALLOCATED.fetch_add(layout.size(), Ordering::SeqCst);
System.alloc(layout)
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
ALLOCATED.fetch_sub(layout.size(), Ordering::SeqCst);
System.dealloc(ptr, layout)
}
}
#[global_allocator]
static A: CountingAlloc = CountingAlloc;
fn main() {
let before = ALLOCATED.load(Ordering::SeqCst);
// Code to measure
let after = ALLOCATED.load(Ordering::SeqCst);
println!("Allocated {} bytes", after - before);
}
// Slow: Many allocations
let mut s = String::new();
for i in 0..100 {
s = s + &i.to_string(); // New allocation each time
}
// Better: Single buffer
let mut s = String::with_capacity(300);
for i in 0..100 {
use std::fmt::Write;
write!(s, "{}", i).unwrap();
}
// Best: Join iterator
let s: String = (0..100).map(|i| i.to_string()).collect();
use std::collections::HashMap;
// Pre-size when you know the count
let mut map = HashMap::with_capacity(1000);
// Use entry API (single lookup)
*map.entry(key).or_insert(0) += 1;
// Consider FxHashMap for integer keys
use rustc_hash::FxHashMap;
let mut map: FxHashMap<u64, Value> = FxHashMap::default();
// Iterators often optimize better
let sum: i32 = data.iter().map(|x| x * 2).filter(|x| *x > 10).sum();
// But sometimes explicit loops are clearer and equally fast
let mut sum = 0;
for x in &data {
let doubled = x * 2;
if doubled > 10 {
sum += doubled;
}
}
// Bounds check on each access
for i in 0..v.len() {
process(v[i]);
}
// No bounds checks (iterator)
for item in &v {
process(*item);
}
// Unsafe: skip bounds check (only when proven safe!)
unsafe {
for i in 0..v.len() {
process(*v.get_unchecked(i));
}
}
// Heap allocated (Box)
let data = Box::new([0u8; 1024]);
// Stack allocated (if small enough)
let data = [0u8; 1024];
// Use arrays for fixed-size, known at compile time
// Use Vec for dynamic size
// Avoid holding locks across awaits
let data = {
let guard = mutex.lock().await;
guard.clone() // Clone and release lock
}; // Lock released here
process(data).await; // Await without lock
// Use tokio::spawn for CPU-bound work
let result = tokio::task::spawn_blocking(|| {
expensive_computation()
}).await?;
// Buffer I/O
use tokio::io::BufReader;
let reader = BufReader::new(file);
| Use Case | Data Structure | Why |
|---|---|---|
| Sequential access | Vec<T> | Cache-friendly |
| Key-value lookup | HashMap / FxHashMap | O(1) average |
| Sorted + lookup | BTreeMap | O(log n), ordered |
| Unique elements | HashSet | O(1) contains |
| FIFO queue | VecDeque | O(1) push/pop both ends |
| Small fixed set | ArrayVec / SmallVec | No heap allocation |
| Bit flags | bitflags | Memory efficient |
// Likely/unlikely branches (nightly)
#![feature(core_intrinsics)]
use std::intrinsics::{likely, unlikely};
if unlikely(error_condition) {
handle_error();
}
// Inline hints
#[inline] // Suggest inlining
#[inline(always)] // Force inlining
#[inline(never)] // Prevent inlining
#[cold] // Rarely called (optimize for space)
fn rarely_used() { ... }
// Target-specific optimization
#[cfg(target_feature = "avx2")]
fn simd_process(data: &[f32]) { ... }
For batch allocations freed together (parsers, request processing):
use bumpalo::Bump;
// All allocations from one arena — freed together when arena drops
fn parse<'a>(input: &str, arena: &'a Bump) -> Vec<&'a Node> {
tokenize(input).map(|t| arena.alloc(Node::new(t))).collect()
}
// Per-request: allocate freely, free everything at once
async fn handle(req: Request) -> Response {
let arena = Bump::new();
let parsed = parse(&req.body, &arena);
generate_response(parsed)
} // arena dropped: all parsed nodes freed in one dealloc
use smallvec::SmallVec;
// Up to 4 items stored inline (stack), spills to heap beyond that
let mut tags: SmallVec<[&str; 4]> = SmallVec::new();
tags.push("performance");
tags.push("rust");
// No heap allocation for ≤ 4 items
use std::fmt::Write;
// Bad: allocates a new String every time
let s = format!("key={} val={}", key, val);
// Good: write into pre-allocated buffer, clear and reuse
let mut buf = String::with_capacity(128);
for (key, val) in &map {
buf.clear();
write!(buf, "key={key} val={val}").unwrap();
send(&buf);
}
// Bad: two lookups
if let Some(v) = map.get_mut(&k) { *v += 1; } else { map.insert(k, 1); }
// Good: single lookup
*map.entry(k).or_insert(0) += 1;
use std::mem::size_of;
// Catch size regressions at compile time
const _: () = assert!(size_of::<MyEvent>() <= 64);
// Large enum variant → box it
enum Message {
Ping,
Text(String),
// HugeThing(VeryLargeStruct), // Makes ALL variants huge!
HugeThing(Box<VeryLargeStruct>), // Pointer-sized, heap only when needed
}
In release mode, use target-cpu=native to allow the compiler to generate machine instructions specifically optimized for the host CPU (enabling AVX, SSE4, etc.).
RUSTFLAGS="-C target-cpu=native" cargo build --release
Mark rarely executed logic (like initialization, configuration loading, or panic routes) with the #[cold] attribute. This instructs the compiler to optimize the layout of code instructions to prioritize hot execution flows.
#[cold]
fn parse_dev_configurations() {
// rarely called; keeps instruction caches clean
}
For high-performance numerical routines, consider using std::simd to process multiple elements in parallel.
#![feature(portable_simd)]
use std::simd::f32x4;
pub fn add_vectors(a: &[f32; 4], b: &[f32; 4]) -> [f32; 4] {
let sa = f32x4::from_slice(a);
let sb = f32x4::from_slice(b);
let sum = sa + sb;
let mut out = [0.0; 4];
sum.copy_to_slice(&mut out);
out
}
&str instead of String where possible?.collect() in hot paths?Source: adxptived/Rust-Skills — distributed by TomeVault.