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:
233
crates/bench/src/suites/memory.rs
Normal file
233
crates/bench/src/suites/memory.rs
Normal file
@@ -0,0 +1,233 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::metrics::MetricsCollector;
|
||||
use crate::mmap_reader::{
|
||||
MmapReaderPhaseAware, MmapReaderPlain, MmapReaderPopulate, MmapReaderRandom,
|
||||
MmapReaderSequential,
|
||||
};
|
||||
use crate::pread_reader::{PreadReaderPlain, PreadReaderRandom, PreadReaderSequential};
|
||||
use crate::runner::BenchConfig;
|
||||
use crate::types::BenchmarkResult;
|
||||
use crate::FileReaderBackend;
|
||||
|
||||
pub fn run(config: &BenchConfig) -> Vec<BenchmarkResult> {
|
||||
let mut results = Vec::new();
|
||||
|
||||
results.extend(bench_idle_rss::<MmapReaderPlain>("mmap", "plain", config));
|
||||
results.extend(bench_idle_rss::<MmapReaderSequential>(
|
||||
"mmap",
|
||||
"sequential",
|
||||
config,
|
||||
));
|
||||
results.extend(bench_idle_rss::<MmapReaderRandom>("mmap", "random", config));
|
||||
results.extend(bench_idle_rss::<MmapReaderPopulate>(
|
||||
"mmap", "populate", config,
|
||||
));
|
||||
results.extend(bench_idle_rss::<MmapReaderPhaseAware>(
|
||||
"mmap",
|
||||
"phase_aware",
|
||||
config,
|
||||
));
|
||||
results.extend(bench_idle_rss::<PreadReaderPlain>("pread", "plain", config));
|
||||
results.extend(bench_idle_rss::<PreadReaderRandom>(
|
||||
"pread", "random", config,
|
||||
));
|
||||
results.extend(bench_idle_rss::<PreadReaderSequential>(
|
||||
"pread",
|
||||
"sequential",
|
||||
config,
|
||||
));
|
||||
|
||||
results.extend(bench_scroll_rss::<MmapReaderPlain>("mmap", "plain", config));
|
||||
results.extend(bench_scroll_rss::<PreadReaderPlain>(
|
||||
"pread", "plain", config,
|
||||
));
|
||||
|
||||
results.extend(bench_jump_end_rss::<MmapReaderPlain>(
|
||||
"mmap", "plain", config,
|
||||
));
|
||||
results.extend(bench_jump_end_rss::<PreadReaderPlain>(
|
||||
"pread", "plain", config,
|
||||
));
|
||||
|
||||
results.extend(bench_rss_reclaim::<MmapReaderPlain>(
|
||||
"mmap", "plain", config,
|
||||
));
|
||||
results.extend(bench_rss_reclaim::<PreadReaderPlain>(
|
||||
"pread", "plain", config,
|
||||
));
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
fn bench_idle_rss<B: FileReaderBackend>(
|
||||
backend: &str,
|
||||
variant: &str,
|
||||
config: &BenchConfig,
|
||||
) -> Vec<BenchmarkResult> {
|
||||
let reader = B::open(&config.test_file).expect("Failed to open file");
|
||||
let rss = MetricsCollector::read_rss();
|
||||
let faults = MetricsCollector::read_page_faults();
|
||||
|
||||
let mut extra = HashMap::new();
|
||||
extra.insert("total_lines".into(), reader.total_lines() as f64);
|
||||
extra.insert(
|
||||
"file_size_mb".into(),
|
||||
reader.file_size() as f64 / (1024.0 * 1024.0),
|
||||
);
|
||||
|
||||
reader.close();
|
||||
|
||||
vec![BenchmarkResult {
|
||||
category: "memory".into(),
|
||||
test_name: "idle_rss".into(),
|
||||
backend: backend.into(),
|
||||
variant: variant.into(),
|
||||
latency_us: vec![],
|
||||
rss_kb: rss.vm_rss_kb,
|
||||
rss_peak_kb: rss.vm_hwm_kb,
|
||||
page_faults: faults.minor_faults + faults.major_faults,
|
||||
extra,
|
||||
}]
|
||||
}
|
||||
|
||||
fn bench_scroll_rss<B: FileReaderBackend>(
|
||||
backend: &str,
|
||||
variant: &str,
|
||||
config: &BenchConfig,
|
||||
) -> Vec<BenchmarkResult> {
|
||||
let reader = B::open(&config.test_file).expect("Failed to open file");
|
||||
let total = reader.total_lines();
|
||||
let sample_interval = 100_000;
|
||||
let max_lines = if config.quick_mode { 100_000 } else { total };
|
||||
|
||||
let mut rss_samples = Vec::new();
|
||||
let mut hwm_samples = Vec::new();
|
||||
|
||||
for i in (0..max_lines).step_by(sample_interval) {
|
||||
let _ = reader.get_line(i);
|
||||
let rss = MetricsCollector::read_rss();
|
||||
rss_samples.push(rss.vm_rss_kb);
|
||||
hwm_samples.push(rss.vm_hwm_kb);
|
||||
}
|
||||
|
||||
let final_rss = MetricsCollector::read_rss();
|
||||
let faults = MetricsCollector::read_page_faults();
|
||||
|
||||
let mut extra = HashMap::new();
|
||||
extra.insert("rss_samples_count".into(), rss_samples.len() as f64);
|
||||
extra.insert(
|
||||
"max_rss_kb".into(),
|
||||
rss_samples.iter().copied().fold(0u64, u64::max) as f64,
|
||||
);
|
||||
extra.insert(
|
||||
"max_hwm_kb".into(),
|
||||
hwm_samples.iter().copied().fold(0u64, u64::max) as f64,
|
||||
);
|
||||
extra.insert("lines_read".into(), max_lines.min(total) as f64);
|
||||
|
||||
reader.close();
|
||||
|
||||
vec![BenchmarkResult {
|
||||
category: "memory".into(),
|
||||
test_name: "scroll_rss".into(),
|
||||
backend: backend.into(),
|
||||
variant: variant.into(),
|
||||
latency_us: vec![],
|
||||
rss_kb: final_rss.vm_rss_kb,
|
||||
rss_peak_kb: final_rss.vm_hwm_kb,
|
||||
page_faults: faults.minor_faults + faults.major_faults,
|
||||
extra,
|
||||
}]
|
||||
}
|
||||
|
||||
fn bench_jump_end_rss<B: FileReaderBackend>(
|
||||
backend: &str,
|
||||
variant: &str,
|
||||
config: &BenchConfig,
|
||||
) -> Vec<BenchmarkResult> {
|
||||
let reader = B::open(&config.test_file).expect("Failed to open file");
|
||||
let total = reader.total_lines();
|
||||
let last_line = total.saturating_sub(1);
|
||||
|
||||
let _ = reader.get_line(last_line);
|
||||
|
||||
let rss = MetricsCollector::read_rss();
|
||||
let faults = MetricsCollector::read_page_faults();
|
||||
|
||||
let mut extra = HashMap::new();
|
||||
extra.insert("last_line_idx".into(), last_line as f64);
|
||||
extra.insert("total_lines".into(), total as f64);
|
||||
|
||||
reader.close();
|
||||
|
||||
vec![BenchmarkResult {
|
||||
category: "memory".into(),
|
||||
test_name: "jump_end_rss".into(),
|
||||
backend: backend.into(),
|
||||
variant: variant.into(),
|
||||
latency_us: vec![],
|
||||
rss_kb: rss.vm_rss_kb,
|
||||
rss_peak_kb: rss.vm_hwm_kb,
|
||||
page_faults: faults.minor_faults + faults.major_faults,
|
||||
extra,
|
||||
}]
|
||||
}
|
||||
|
||||
fn bench_rss_reclaim<B: FileReaderBackend>(
|
||||
backend: &str,
|
||||
variant: &str,
|
||||
config: &BenchConfig,
|
||||
) -> Vec<BenchmarkResult> {
|
||||
let reader = B::open(&config.test_file).expect("Failed to open file");
|
||||
let total = reader.total_lines();
|
||||
let last_line = total.saturating_sub(1);
|
||||
|
||||
let _ = reader.get_line(last_line);
|
||||
|
||||
let wait_secs: u64 = if config.quick_mode { 5 } else { 30 };
|
||||
let sample_interval: u64 = 5;
|
||||
let num_samples = (wait_secs / sample_interval) as usize;
|
||||
|
||||
let mut rss_samples = Vec::with_capacity(num_samples);
|
||||
let mut hwm_samples = Vec::with_capacity(num_samples);
|
||||
|
||||
for _ in 0..num_samples {
|
||||
std::thread::sleep(std::time::Duration::from_secs(sample_interval));
|
||||
let rss = MetricsCollector::read_rss();
|
||||
rss_samples.push(rss.vm_rss_kb);
|
||||
hwm_samples.push(rss.vm_hwm_kb);
|
||||
}
|
||||
|
||||
let final_rss = MetricsCollector::read_rss();
|
||||
let faults = MetricsCollector::read_page_faults();
|
||||
reader.close();
|
||||
|
||||
let mut extra = HashMap::new();
|
||||
extra.insert("wait_total_secs".into(), wait_secs as f64);
|
||||
extra.insert("rss_samples".into(), rss_samples.len() as f64);
|
||||
if let (Some(&first), Some(&last)) = (rss_samples.first(), rss_samples.last()) {
|
||||
extra.insert("rss_first_kb".into(), first as f64);
|
||||
extra.insert("rss_last_kb".into(), last as f64);
|
||||
extra.insert(
|
||||
"rss_change_pct".into(),
|
||||
if first > 0 {
|
||||
((last as f64 - first as f64) / first as f64) * 100.0
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
vec![BenchmarkResult {
|
||||
category: "memory".into(),
|
||||
test_name: "rss_reclaim".into(),
|
||||
backend: backend.into(),
|
||||
variant: variant.into(),
|
||||
latency_us: vec![],
|
||||
rss_kb: final_rss.vm_rss_kb,
|
||||
rss_peak_kb: final_rss.vm_hwm_kb,
|
||||
page_faults: faults.minor_faults + faults.major_faults,
|
||||
extra,
|
||||
}]
|
||||
}
|
||||
Reference in New Issue
Block a user