fix(bench): eliminate SIGBUS handler static mut UB with Once + raw atomics (closes #33)

Replace `static mut OLD_SIGBUS_HANDLER` with AtomicU8 + AtomicPtr to
remove data race UB when concurrent benchmarks call open() from multiple
threads.

Key changes:
- Use `Once::call_once` to guarantee single handler installation
- Publish old handler to atomics BEFORE installing new handler (closes
  the handler-active-but-state-unpublished race window)
- Read atomics with Acquire in signal handler (async-signal-safe)
- Align si_addr to page boundary before mmap(MAP_FIXED)
- Add concurrent test: 8 threads open all 5 variants simultaneously
This commit is contained in:
dailz
2026-06-05 13:22:02 +08:00
parent 534a089b58
commit dad5f5a635
19 changed files with 3562 additions and 0 deletions

207
crates/bench/src/report.rs Normal file
View File

@@ -0,0 +1,207 @@
use crate::metrics;
use crate::types::BenchmarkResult;
fn format_rss_mb(kb: u64) -> String {
format!("{:.1}MB", kb as f64 / 1024.0)
}
fn format_faults(count: u64) -> String {
if count < 1000 {
count.to_string()
} else {
let s = count.to_string();
let mut result = String::new();
for (i, c) in s.chars().rev().enumerate() {
if i > 0 && i % 3 == 0 {
result.push(',');
}
result.push(c);
}
result.chars().rev().collect()
}
}
// Format benchmark results as Markdown, grouped by category.
pub fn format_report(results: &[BenchmarkResult]) -> String {
let mut report = String::new();
report.push_str("# Benchmark: mmap vs pread\n\n");
let mut categories: std::collections::BTreeMap<&str, Vec<&BenchmarkResult>> =
std::collections::BTreeMap::new();
for r in results {
categories.entry(&r.category).or_default().push(r);
}
for (category, category_results) in &categories {
report.push_str(&format!("## {}\n\n", capitalize(category)));
let mut tests: std::collections::BTreeMap<&str, Vec<&BenchmarkResult>> =
std::collections::BTreeMap::new();
for r in category_results {
tests.entry(&r.test_name).or_default().push(r);
}
let mut variants: Vec<(String, String)> = Vec::new();
for r in category_results {
let key = format!("{} ({})", r.backend, r.variant);
if !variants
.iter()
.any(|(b, v)| format!("{} ({})", b, v) == key)
{
variants.push((r.backend.clone(), r.variant.clone()));
}
}
report.push_str("### Latency\n\n");
report.push_str("| Test |");
for (backend, variant) in &variants {
report.push_str(&format!(" {} ({}) |", backend, variant));
}
report.push_str(" Winner |\n");
report.push_str("|------|");
for _ in &variants {
report.push_str("------|");
}
report.push_str("--------|\n");
for (test_name, test_results) in &tests {
report.push_str(&format!("| {} |", test_name));
let mut all_means: Vec<f64> = Vec::new();
for (backend, variant) in &variants {
if let Some(r) = test_results
.iter()
.find(|r| r.backend == *backend && r.variant == *variant)
{
let avg = if r.latency_us.is_empty() {
0.0
} else {
metrics::mean(&r.latency_us)
};
all_means.push(avg);
}
}
let use_ms = all_means.iter().any(|&v| v >= 1000.0);
let mut best_backend = String::new();
let mut best_latency = f64::MAX;
for (backend, variant) in &variants {
let matching = test_results
.iter()
.find(|r| r.backend == *backend && r.variant == *variant);
if let Some(r) = matching {
let avg = if r.latency_us.is_empty() {
0.0
} else {
metrics::mean(&r.latency_us)
};
let sd = metrics::stdev(&r.latency_us);
let p95_val = metrics::p95(&r.latency_us) as f64;
let cell = if use_ms {
format!(
"{:.2}\u{00b1}{:.2}ms (p95:{:.2}ms)",
avg / 1000.0,
sd / 1000.0,
p95_val / 1000.0
)
} else {
format!(
"{:.1}\u{00b1}{:.1}\u{b5}s (p95:{:.1}\u{b5}s)",
avg, sd, p95_val
)
};
report.push_str(&format!(" {} |", cell));
if avg > 0.0 && avg < best_latency {
best_latency = avg;
best_backend = format!("{} ({})", backend, variant);
}
} else {
report.push_str(" - |");
}
}
report.push_str(&format!(
" {} |\n",
if best_backend.is_empty() {
"-"
} else {
&best_backend
}
));
}
report.push('\n');
let has_memory = category_results
.iter()
.any(|r| r.rss_kb > 0 || r.rss_peak_kb > 0);
if has_memory {
report.push_str("### Memory\n\n");
report.push_str("| Test | Variant | RSS | Peak RSS | Page Faults |\n");
report.push_str("|------|---------|-----|----------|-------------|\n");
for r in category_results {
let variant_label = format!("{} ({})", r.backend, r.variant);
report.push_str(&format!(
"| {} | {} | {} | {} | {} |\n",
r.test_name,
variant_label,
format_rss_mb(r.rss_kb),
format_rss_mb(r.rss_peak_kb),
format_faults(r.page_faults),
));
}
report.push('\n');
}
let extras: Vec<(String, String, Vec<(String, f64)>)> = category_results
.iter()
.filter(|r| !r.extra.is_empty())
.map(|r| {
let mut pairs: Vec<(String, f64)> =
r.extra.iter().map(|(k, &v)| (k.clone(), v)).collect();
pairs.sort_by(|a, b| a.0.cmp(&b.0));
(
r.test_name.clone(),
format!("{} ({})", r.backend, r.variant),
pairs,
)
})
.collect();
if !extras.is_empty() {
report.push_str("### Extra Metrics\n\n");
report.push_str("| Test | Variant | Metric | Value |\n");
report.push_str("|------|---------|--------|-------|\n");
for (test_name, variant_label, pairs) in &extras {
for (key, val) in pairs {
report.push_str(&format!(
"| {} | {} | {} | {:.3} |\n",
test_name, variant_label, key, val
));
}
}
report.push('\n');
}
}
report.push_str("## Summary\n\n");
report.push_str(&format!("- Total benchmarks: {}\n", results.len()));
report.push_str("- Categories: ");
report.push_str(&categories.keys().cloned().collect::<Vec<_>>().join(", "));
report.push('\n');
report
}
fn capitalize(s: &str) -> String {
let mut c = s.chars();
match c.next() {
None => String::new(),
Some(f) => f.to_uppercase().chain(c).collect(),
}
}