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:
117
crates/bench/src/suites/concurrent.rs
Normal file
117
crates/bench/src/suites/concurrent.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
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_parallel_reads::<MmapReaderPlain>(
|
||||
"mmap", "plain", config,
|
||||
));
|
||||
results.extend(bench_parallel_reads::<MmapReaderSequential>(
|
||||
"mmap",
|
||||
"sequential",
|
||||
config,
|
||||
));
|
||||
results.extend(bench_parallel_reads::<MmapReaderRandom>(
|
||||
"mmap", "random", config,
|
||||
));
|
||||
results.extend(bench_parallel_reads::<MmapReaderPopulate>(
|
||||
"mmap", "populate", config,
|
||||
));
|
||||
results.extend(bench_parallel_reads::<MmapReaderPhaseAware>(
|
||||
"mmap",
|
||||
"phase_aware",
|
||||
config,
|
||||
));
|
||||
results.extend(bench_parallel_reads::<PreadReaderPlain>(
|
||||
"pread", "plain", config,
|
||||
));
|
||||
results.extend(bench_parallel_reads::<PreadReaderRandom>(
|
||||
"pread", "random", config,
|
||||
));
|
||||
results.extend(bench_parallel_reads::<PreadReaderSequential>(
|
||||
"pread",
|
||||
"sequential",
|
||||
config,
|
||||
));
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
fn bench_parallel_reads<B: FileReaderBackend + Send + 'static>(
|
||||
backend: &str,
|
||||
variant: &str,
|
||||
config: &BenchConfig,
|
||||
) -> Vec<BenchmarkResult> {
|
||||
let path = config.test_file.clone();
|
||||
let iterations = if config.quick_mode { 250 } else { 1000 };
|
||||
|
||||
let total_lines = {
|
||||
let reader = B::open(&path).expect("Failed to open file for line count");
|
||||
let count = reader.total_lines();
|
||||
reader.close();
|
||||
count
|
||||
};
|
||||
|
||||
let overall_start = std::time::Instant::now();
|
||||
let num_threads = 4usize;
|
||||
|
||||
let handles: Vec<_> = (0..num_threads)
|
||||
.map(|thread_id| {
|
||||
let path = path.clone();
|
||||
std::thread::spawn(move || {
|
||||
let reader = B::open(&path).expect("Failed to open file in thread");
|
||||
let mut latencies = Vec::with_capacity(iterations);
|
||||
|
||||
for i in 0..iterations {
|
||||
let line_idx = (thread_id * iterations + i) % total_lines.max(1);
|
||||
let t = std::time::Instant::now();
|
||||
let _ = reader.get_line(line_idx);
|
||||
latencies.push(t.elapsed().as_micros() as u64);
|
||||
}
|
||||
|
||||
reader.close();
|
||||
latencies
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
let thread_latencies: Vec<Vec<u64>> = handles
|
||||
.into_iter()
|
||||
.map(|h| h.join().expect("Thread panicked"))
|
||||
.collect();
|
||||
|
||||
let total_elapsed = overall_start.elapsed();
|
||||
|
||||
let all_latencies: Vec<u64> = thread_latencies.into_iter().flatten().collect();
|
||||
|
||||
let rss = MetricsCollector::read_rss();
|
||||
let faults = MetricsCollector::read_page_faults();
|
||||
|
||||
let mut extra = HashMap::new();
|
||||
extra.insert("num_threads".into(), num_threads as f64);
|
||||
extra.insert("iterations_per_thread".into(), iterations as f64);
|
||||
extra.insert("total_time_us".into(), total_elapsed.as_micros() as f64);
|
||||
extra.insert("total_lines".into(), total_lines as f64);
|
||||
|
||||
vec![BenchmarkResult {
|
||||
category: "concurrent".into(),
|
||||
test_name: "parallel_reads".into(),
|
||||
backend: backend.into(),
|
||||
variant: variant.into(),
|
||||
latency_us: all_latencies,
|
||||
rss_kb: rss.vm_rss_kb,
|
||||
rss_peak_kb: rss.vm_hwm_kb,
|
||||
page_faults: faults.minor_faults + faults.major_faults,
|
||||
extra,
|
||||
}]
|
||||
}
|
||||
269
crates/bench/src/suites/growth.rs
Normal file
269
crates/bench/src/suites/growth.rs
Normal file
@@ -0,0 +1,269 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::data_gen;
|
||||
use crate::metrics::MetricsCollector;
|
||||
use crate::mmap_reader::MmapReaderPlain;
|
||||
use crate::pread_reader::PreadReaderPlain;
|
||||
use crate::runner::BenchConfig;
|
||||
use crate::types::BenchmarkResult;
|
||||
use crate::FileReaderBackend;
|
||||
|
||||
pub fn run(config: &BenchConfig) -> Vec<BenchmarkResult> {
|
||||
let mut results = Vec::new();
|
||||
|
||||
let dir = tempfile::tempdir().expect("Failed to create temp dir");
|
||||
results.extend(bench_append_visibility_mmap(config, dir.path()));
|
||||
results.extend(bench_append_visibility_pread(config, dir.path()));
|
||||
results.extend(bench_remap_cost(config, dir.path()));
|
||||
results.extend(bench_scroll_during_append(config, dir.path()));
|
||||
results.extend(bench_high_frequency_append(config, dir.path()));
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
fn bench_append_visibility_mmap(
|
||||
config: &BenchConfig,
|
||||
dir: &std::path::Path,
|
||||
) -> Vec<BenchmarkResult> {
|
||||
let path = data_gen::generate_growable_file(dir).expect("Failed to create growable file");
|
||||
let append_count: usize = if config.quick_mode { 100 } else { 1000 };
|
||||
|
||||
let mut reader = MmapReaderPlain::open(&path).expect("Failed to open growable file");
|
||||
let original_lines = reader.total_lines();
|
||||
let original_size = reader.file_size();
|
||||
|
||||
data_gen::append_lines(&path, append_count).expect("Failed to append lines");
|
||||
|
||||
let new_metadata = std::fs::metadata(&path).expect("Failed to read metadata");
|
||||
let new_size = new_metadata.len() as usize;
|
||||
|
||||
let remap_start = std::time::Instant::now();
|
||||
reader.remap(new_size).expect("Failed to remap");
|
||||
let remap_elapsed = remap_start.elapsed();
|
||||
|
||||
let new_line_bytes =
|
||||
reader.read_range(original_size, (new_size - original_size as usize).min(256));
|
||||
let visible = new_line_bytes.is_some();
|
||||
|
||||
let rss = MetricsCollector::read_rss();
|
||||
let faults = MetricsCollector::read_page_faults();
|
||||
|
||||
let mut extra = HashMap::new();
|
||||
extra.insert("original_lines".into(), original_lines as f64);
|
||||
extra.insert("appended_lines".into(), append_count as f64);
|
||||
extra.insert("new_bytes_visible".into(), visible as u64 as f64);
|
||||
extra.insert("original_size".into(), original_size as f64);
|
||||
extra.insert("new_size".into(), new_size as f64);
|
||||
extra.insert("remap_us".into(), remap_elapsed.as_micros() as f64);
|
||||
|
||||
reader.close();
|
||||
|
||||
vec![BenchmarkResult {
|
||||
category: "growth".into(),
|
||||
test_name: "append_visibility_mmap".into(),
|
||||
backend: "mmap".into(),
|
||||
variant: "plain".into(),
|
||||
latency_us: vec![remap_elapsed.as_micros() as u64],
|
||||
rss_kb: rss.vm_rss_kb,
|
||||
rss_peak_kb: rss.vm_hwm_kb,
|
||||
page_faults: faults.minor_faults + faults.major_faults,
|
||||
extra,
|
||||
}]
|
||||
}
|
||||
|
||||
fn bench_append_visibility_pread(
|
||||
config: &BenchConfig,
|
||||
dir: &std::path::Path,
|
||||
) -> Vec<BenchmarkResult> {
|
||||
let sub_dir = dir.join("pread_growth");
|
||||
let path = data_gen::generate_growable_file(&sub_dir).expect("Failed to create growable file");
|
||||
let append_count: usize = if config.quick_mode { 100 } else { 1000 };
|
||||
|
||||
let reader = PreadReaderPlain::open(&path).expect("Failed to open growable file");
|
||||
let original_lines = reader.total_lines();
|
||||
reader.close();
|
||||
|
||||
data_gen::append_lines(&path, append_count).expect("Failed to append lines");
|
||||
|
||||
let reopen_start = std::time::Instant::now();
|
||||
let new_reader = PreadReaderPlain::open(&path).expect("Failed to reopen file");
|
||||
let reopen_elapsed = reopen_start.elapsed();
|
||||
|
||||
let new_lines = new_reader.total_lines();
|
||||
let can_read_new = new_lines > original_lines;
|
||||
if can_read_new {
|
||||
let last_line = new_lines.saturating_sub(1);
|
||||
let _ = new_reader.get_line(last_line);
|
||||
}
|
||||
|
||||
let rss = MetricsCollector::read_rss();
|
||||
let faults = MetricsCollector::read_page_faults();
|
||||
|
||||
let mut extra = HashMap::new();
|
||||
extra.insert("original_lines".into(), original_lines as f64);
|
||||
extra.insert("appended_lines".into(), append_count as f64);
|
||||
extra.insert("new_total_lines".into(), new_lines as f64);
|
||||
extra.insert("reopen_us".into(), reopen_elapsed.as_micros() as f64);
|
||||
|
||||
new_reader.close();
|
||||
|
||||
vec![BenchmarkResult {
|
||||
category: "growth".into(),
|
||||
test_name: "append_visibility_pread".into(),
|
||||
backend: "pread".into(),
|
||||
variant: "plain".into(),
|
||||
latency_us: vec![reopen_elapsed.as_micros() as u64],
|
||||
rss_kb: rss.vm_rss_kb,
|
||||
rss_peak_kb: rss.vm_hwm_kb,
|
||||
page_faults: faults.minor_faults + faults.major_faults,
|
||||
extra,
|
||||
}]
|
||||
}
|
||||
|
||||
fn bench_remap_cost(config: &BenchConfig, dir: &std::path::Path) -> Vec<BenchmarkResult> {
|
||||
let sub_dir = dir.join("remap_cost");
|
||||
let append_count: usize = if config.quick_mode { 100 } else { 1000 };
|
||||
let iterations: usize = if config.quick_mode { 5 } else { 20 };
|
||||
|
||||
let mut latencies = Vec::with_capacity(iterations);
|
||||
|
||||
for _ in 0..iterations {
|
||||
let path = data_gen::generate_growable_file(&sub_dir).expect("Failed to create file");
|
||||
let mut reader = MmapReaderPlain::open(&path).expect("Failed to open file");
|
||||
|
||||
data_gen::append_lines(&path, append_count).expect("Failed to append");
|
||||
|
||||
let new_size = std::fs::metadata(&path).expect("metadata").len() as usize;
|
||||
let t = std::time::Instant::now();
|
||||
reader.remap(new_size).expect("remap failed");
|
||||
latencies.push(t.elapsed().as_micros() as u64);
|
||||
|
||||
reader.close();
|
||||
let _ = std::fs::remove_file(&path);
|
||||
}
|
||||
|
||||
let rss = MetricsCollector::read_rss();
|
||||
let faults = MetricsCollector::read_page_faults();
|
||||
|
||||
let mut extra = HashMap::new();
|
||||
extra.insert("appended_per_iter".into(), append_count as f64);
|
||||
extra.insert("iterations".into(), iterations as f64);
|
||||
|
||||
vec![BenchmarkResult {
|
||||
category: "growth".into(),
|
||||
test_name: "remap_cost".into(),
|
||||
backend: "mmap".into(),
|
||||
variant: "plain".into(),
|
||||
latency_us: latencies,
|
||||
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_during_append(config: &BenchConfig, dir: &std::path::Path) -> Vec<BenchmarkResult> {
|
||||
let sub_dir = dir.join("scroll_append");
|
||||
let path = data_gen::generate_growable_file(&sub_dir).expect("Failed to create growable file");
|
||||
let duration_secs: u64 = if config.quick_mode { 2 } else { 10 };
|
||||
let append_rate: usize = if config.quick_mode { 1000 } else { 10000 };
|
||||
|
||||
let bg_path = path.clone();
|
||||
let bg_handle = std::thread::spawn(move || {
|
||||
let batch_size = 100;
|
||||
let batch_interval =
|
||||
std::time::Duration::from_micros(1_000_000 / (append_rate / batch_size).max(1) as u64);
|
||||
let start = std::time::Instant::now();
|
||||
while start.elapsed().as_secs() < duration_secs {
|
||||
data_gen::append_lines(&bg_path, batch_size).ok();
|
||||
std::thread::sleep(batch_interval);
|
||||
}
|
||||
});
|
||||
|
||||
let reader = PreadReaderPlain::open(&path).expect("Failed to open file");
|
||||
let mut frame_latencies = Vec::new();
|
||||
let mut current_line = 0usize;
|
||||
let scroll_start = std::time::Instant::now();
|
||||
|
||||
while scroll_start.elapsed().as_secs() < duration_secs {
|
||||
let t = std::time::Instant::now();
|
||||
let _ = reader.get_line(current_line);
|
||||
frame_latencies.push(t.elapsed().as_micros() as u64);
|
||||
current_line += 1;
|
||||
}
|
||||
|
||||
reader.close();
|
||||
bg_handle.join().ok();
|
||||
|
||||
let rss = MetricsCollector::read_rss();
|
||||
let faults = MetricsCollector::read_page_faults();
|
||||
|
||||
let mut extra = HashMap::new();
|
||||
extra.insert("duration_secs".into(), duration_secs as f64);
|
||||
extra.insert("append_rate_per_sec".into(), append_rate as f64);
|
||||
extra.insert("frames_rendered".into(), frame_latencies.len() as f64);
|
||||
|
||||
vec![BenchmarkResult {
|
||||
category: "growth".into(),
|
||||
test_name: "scroll_during_append".into(),
|
||||
backend: "pread".into(),
|
||||
variant: "plain".into(),
|
||||
latency_us: frame_latencies,
|
||||
rss_kb: rss.vm_rss_kb,
|
||||
rss_peak_kb: rss.vm_hwm_kb,
|
||||
page_faults: faults.minor_faults + faults.major_faults,
|
||||
extra,
|
||||
}]
|
||||
}
|
||||
|
||||
fn bench_high_frequency_append(
|
||||
config: &BenchConfig,
|
||||
dir: &std::path::Path,
|
||||
) -> Vec<BenchmarkResult> {
|
||||
let sub_dir = dir.join("high_freq_append");
|
||||
let path = data_gen::generate_growable_file(&sub_dir).expect("Failed to create growable file");
|
||||
let duration_secs: u64 = if config.quick_mode { 3 } else { 30 };
|
||||
let append_rate: usize = if config.quick_mode { 1000 } else { 10000 };
|
||||
let batch_size: usize = 100;
|
||||
let batches_per_sec = append_rate / batch_size;
|
||||
let total_batches = (duration_secs as usize * batches_per_sec).max(1);
|
||||
|
||||
let mut detect_latencies = Vec::with_capacity(total_batches);
|
||||
|
||||
for _ in 0..total_batches {
|
||||
data_gen::append_lines(&path, batch_size).expect("Failed to append");
|
||||
|
||||
let t = std::time::Instant::now();
|
||||
if let Ok(reader) = PreadReaderPlain::open(&path) {
|
||||
let total = reader.total_lines();
|
||||
let _ = reader.get_line(total.saturating_sub(1));
|
||||
reader.close();
|
||||
}
|
||||
detect_latencies.push(t.elapsed().as_micros() as u64);
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_micros(
|
||||
1_000_000 / batches_per_sec as u64,
|
||||
));
|
||||
}
|
||||
|
||||
let rss = MetricsCollector::read_rss();
|
||||
let faults = MetricsCollector::read_page_faults();
|
||||
|
||||
let mut extra = HashMap::new();
|
||||
extra.insert("duration_secs".into(), duration_secs as f64);
|
||||
extra.insert("append_rate_per_sec".into(), append_rate as f64);
|
||||
extra.insert("batch_size".into(), batch_size as f64);
|
||||
extra.insert("total_batches".into(), total_batches as f64);
|
||||
|
||||
vec![BenchmarkResult {
|
||||
category: "growth".into(),
|
||||
test_name: "high_frequency_append".into(),
|
||||
backend: "pread".into(),
|
||||
variant: "plain".into(),
|
||||
latency_us: detect_latencies,
|
||||
rss_kb: rss.vm_rss_kb,
|
||||
rss_peak_kb: rss.vm_hwm_kb,
|
||||
page_faults: faults.minor_faults + faults.major_faults,
|
||||
extra,
|
||||
}]
|
||||
}
|
||||
246
crates/bench/src/suites/jump.rs
Normal file
246
crates/bench/src/suites/jump.rs
Normal file
@@ -0,0 +1,246 @@
|
||||
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_near_jump::<MmapReaderPlain>("mmap", "plain", config));
|
||||
results.extend(bench_near_jump::<MmapReaderSequential>(
|
||||
"mmap",
|
||||
"sequential",
|
||||
config,
|
||||
));
|
||||
results.extend(bench_near_jump::<MmapReaderRandom>(
|
||||
"mmap", "random", config,
|
||||
));
|
||||
results.extend(bench_near_jump::<MmapReaderPopulate>(
|
||||
"mmap", "populate", config,
|
||||
));
|
||||
results.extend(bench_near_jump::<MmapReaderPhaseAware>(
|
||||
"mmap",
|
||||
"phase_aware",
|
||||
config,
|
||||
));
|
||||
results.extend(bench_near_jump::<PreadReaderPlain>(
|
||||
"pread", "plain", config,
|
||||
));
|
||||
results.extend(bench_near_jump::<PreadReaderRandom>(
|
||||
"pread", "random", config,
|
||||
));
|
||||
results.extend(bench_near_jump::<PreadReaderSequential>(
|
||||
"pread",
|
||||
"sequential",
|
||||
config,
|
||||
));
|
||||
|
||||
results.extend(bench_far_jump::<MmapReaderPlain>("mmap", "plain", config));
|
||||
results.extend(bench_far_jump::<MmapReaderSequential>(
|
||||
"mmap",
|
||||
"sequential",
|
||||
config,
|
||||
));
|
||||
results.extend(bench_far_jump::<MmapReaderRandom>("mmap", "random", config));
|
||||
results.extend(bench_far_jump::<MmapReaderPopulate>(
|
||||
"mmap", "populate", config,
|
||||
));
|
||||
results.extend(bench_far_jump::<MmapReaderPhaseAware>(
|
||||
"mmap",
|
||||
"phase_aware",
|
||||
config,
|
||||
));
|
||||
results.extend(bench_far_jump::<PreadReaderPlain>("pread", "plain", config));
|
||||
results.extend(bench_far_jump::<PreadReaderRandom>(
|
||||
"pread", "random", config,
|
||||
));
|
||||
results.extend(bench_far_jump::<PreadReaderSequential>(
|
||||
"pread",
|
||||
"sequential",
|
||||
config,
|
||||
));
|
||||
|
||||
results.extend(bench_jump_end::<MmapReaderPlain>("mmap", "plain", config));
|
||||
results.extend(bench_jump_end::<PreadReaderPlain>("pread", "plain", config));
|
||||
|
||||
results.extend(bench_reverse_scan::<MmapReaderPlain>(
|
||||
"mmap", "plain", config,
|
||||
));
|
||||
results.extend(bench_reverse_scan::<PreadReaderPlain>(
|
||||
"pread", "plain", config,
|
||||
));
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
fn bench_near_jump<B: FileReaderBackend>(
|
||||
backend: &str,
|
||||
variant: &str,
|
||||
config: &BenchConfig,
|
||||
) -> Vec<BenchmarkResult> {
|
||||
let path = &config.test_file;
|
||||
let reader = B::open(path).expect("Failed to open file");
|
||||
let total = reader.total_lines();
|
||||
let iterations: usize = if config.quick_mode { 10 } else { 100 };
|
||||
|
||||
let mut latencies = Vec::with_capacity(iterations);
|
||||
let mut current = 0usize;
|
||||
|
||||
for _ in 0..iterations {
|
||||
let target = (current + 15).min(total.saturating_sub(1));
|
||||
let t = std::time::Instant::now();
|
||||
let _ = reader.get_line(target);
|
||||
latencies.push(t.elapsed().as_micros() as u64);
|
||||
current = target;
|
||||
}
|
||||
|
||||
let rss = MetricsCollector::read_rss();
|
||||
let faults = MetricsCollector::read_page_faults();
|
||||
|
||||
reader.close();
|
||||
|
||||
vec![BenchmarkResult {
|
||||
category: "jump".into(),
|
||||
test_name: "near_jump".into(),
|
||||
backend: backend.into(),
|
||||
variant: variant.into(),
|
||||
latency_us: latencies,
|
||||
rss_kb: rss.vm_rss_kb,
|
||||
rss_peak_kb: rss.vm_hwm_kb,
|
||||
page_faults: faults.minor_faults + faults.major_faults,
|
||||
extra: HashMap::new(),
|
||||
}]
|
||||
}
|
||||
|
||||
fn bench_far_jump<B: FileReaderBackend>(
|
||||
backend: &str,
|
||||
variant: &str,
|
||||
config: &BenchConfig,
|
||||
) -> Vec<BenchmarkResult> {
|
||||
let path = &config.test_file;
|
||||
let reader = B::open(path).expect("Failed to open file");
|
||||
let total = reader.total_lines();
|
||||
let repetitions: usize = if config.quick_mode { 3 } else { 10 };
|
||||
|
||||
let fractions = [0.25, 0.50, 0.75];
|
||||
let mut latencies = Vec::new();
|
||||
let mut extra = HashMap::new();
|
||||
|
||||
for &frac in &fractions {
|
||||
let target = ((total as f64 * frac) as usize).min(total.saturating_sub(1));
|
||||
for _ in 0..repetitions {
|
||||
let t = std::time::Instant::now();
|
||||
let _ = reader.get_line(target);
|
||||
latencies.push(t.elapsed().as_micros() as u64);
|
||||
}
|
||||
}
|
||||
|
||||
extra.insert("jump_positions".into(), 3.0);
|
||||
|
||||
let rss = MetricsCollector::read_rss();
|
||||
let faults = MetricsCollector::read_page_faults();
|
||||
|
||||
reader.close();
|
||||
|
||||
vec![BenchmarkResult {
|
||||
category: "jump".into(),
|
||||
test_name: "far_jump".into(),
|
||||
backend: backend.into(),
|
||||
variant: variant.into(),
|
||||
latency_us: latencies,
|
||||
rss_kb: rss.vm_rss_kb,
|
||||
rss_peak_kb: rss.vm_hwm_kb,
|
||||
page_faults: faults.minor_faults + faults.major_faults,
|
||||
extra,
|
||||
}]
|
||||
}
|
||||
|
||||
fn bench_jump_end<B: FileReaderBackend>(
|
||||
backend: &str,
|
||||
variant: &str,
|
||||
config: &BenchConfig,
|
||||
) -> Vec<BenchmarkResult> {
|
||||
let path = &config.test_file;
|
||||
let reader = B::open(path).expect("Failed to open file");
|
||||
let total = reader.total_lines();
|
||||
let iterations: usize = if config.quick_mode { 5 } else { 10 };
|
||||
|
||||
let last_line = total.saturating_sub(1);
|
||||
let mut latencies = Vec::with_capacity(iterations);
|
||||
|
||||
for _ in 0..iterations {
|
||||
let t = std::time::Instant::now();
|
||||
let _ = reader.get_line(last_line);
|
||||
latencies.push(t.elapsed().as_micros() as u64);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
reader.close();
|
||||
|
||||
vec![BenchmarkResult {
|
||||
category: "jump".into(),
|
||||
test_name: "jump_end".into(),
|
||||
backend: backend.into(),
|
||||
variant: variant.into(),
|
||||
latency_us: latencies,
|
||||
rss_kb: rss.vm_rss_kb,
|
||||
rss_peak_kb: rss.vm_hwm_kb,
|
||||
page_faults: faults.minor_faults + faults.major_faults,
|
||||
extra,
|
||||
}]
|
||||
}
|
||||
|
||||
fn bench_reverse_scan<B: FileReaderBackend>(
|
||||
backend: &str,
|
||||
variant: &str,
|
||||
config: &BenchConfig,
|
||||
) -> Vec<BenchmarkResult> {
|
||||
let path = &config.test_file;
|
||||
let reader = B::open(path).expect("Failed to open file");
|
||||
let total = reader.total_lines();
|
||||
let iterations: usize = if config.quick_mode { 5 } else { 10 };
|
||||
|
||||
let mut latencies = Vec::with_capacity(iterations * 35);
|
||||
|
||||
for _ in 0..iterations {
|
||||
// Read lines backwards from end: last line, last-1, ..., last-34
|
||||
let start = total.saturating_sub(35);
|
||||
for i in (start..total).rev() {
|
||||
let t = std::time::Instant::now();
|
||||
let _ = reader.get_line(i);
|
||||
latencies.push(t.elapsed().as_micros() as u64);
|
||||
}
|
||||
}
|
||||
|
||||
let rss = MetricsCollector::read_rss();
|
||||
let faults = MetricsCollector::read_page_faults();
|
||||
reader.close();
|
||||
|
||||
let mut extra = HashMap::new();
|
||||
extra.insert("lines_per_scan".into(), 35.0);
|
||||
extra.insert("iterations".into(), iterations as f64);
|
||||
|
||||
vec![BenchmarkResult {
|
||||
category: "jump".into(),
|
||||
test_name: "reverse_scan".into(),
|
||||
backend: backend.into(),
|
||||
variant: variant.into(),
|
||||
latency_us: latencies,
|
||||
rss_kb: rss.vm_rss_kb,
|
||||
rss_peak_kb: rss.vm_hwm_kb,
|
||||
page_faults: faults.minor_faults + faults.major_faults,
|
||||
extra,
|
||||
}]
|
||||
}
|
||||
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,
|
||||
}]
|
||||
}
|
||||
7
crates/bench/src/suites/mod.rs
Normal file
7
crates/bench/src/suites/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
pub mod concurrent;
|
||||
pub mod growth;
|
||||
pub mod jump;
|
||||
pub mod memory;
|
||||
pub mod render;
|
||||
pub mod rotation;
|
||||
pub mod startup;
|
||||
177
crates/bench/src/suites/render.rs
Normal file
177
crates/bench/src/suites/render.rs
Normal file
@@ -0,0 +1,177 @@
|
||||
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_single_frame::<MmapReaderPlain>(
|
||||
"mmap", "plain", config,
|
||||
));
|
||||
results.extend(bench_single_frame::<MmapReaderSequential>(
|
||||
"mmap",
|
||||
"sequential",
|
||||
config,
|
||||
));
|
||||
results.extend(bench_single_frame::<MmapReaderRandom>(
|
||||
"mmap", "random", config,
|
||||
));
|
||||
results.extend(bench_single_frame::<MmapReaderPopulate>(
|
||||
"mmap", "populate", config,
|
||||
));
|
||||
results.extend(bench_single_frame::<MmapReaderPhaseAware>(
|
||||
"mmap",
|
||||
"phase_aware",
|
||||
config,
|
||||
));
|
||||
results.extend(bench_single_frame::<PreadReaderPlain>(
|
||||
"pread", "plain", config,
|
||||
));
|
||||
results.extend(bench_single_frame::<PreadReaderRandom>(
|
||||
"pread", "random", config,
|
||||
));
|
||||
results.extend(bench_single_frame::<PreadReaderSequential>(
|
||||
"pread",
|
||||
"sequential",
|
||||
config,
|
||||
));
|
||||
|
||||
results.extend(bench_continuous_scroll::<MmapReaderPlain>(
|
||||
"mmap", "plain", config,
|
||||
));
|
||||
results.extend(bench_continuous_scroll::<MmapReaderSequential>(
|
||||
"mmap",
|
||||
"sequential",
|
||||
config,
|
||||
));
|
||||
results.extend(bench_continuous_scroll::<MmapReaderRandom>(
|
||||
"mmap", "random", config,
|
||||
));
|
||||
results.extend(bench_continuous_scroll::<MmapReaderPopulate>(
|
||||
"mmap", "populate", config,
|
||||
));
|
||||
results.extend(bench_continuous_scroll::<MmapReaderPhaseAware>(
|
||||
"mmap",
|
||||
"phase_aware",
|
||||
config,
|
||||
));
|
||||
results.extend(bench_continuous_scroll::<PreadReaderPlain>(
|
||||
"pread", "plain", config,
|
||||
));
|
||||
results.extend(bench_continuous_scroll::<PreadReaderRandom>(
|
||||
"pread", "random", config,
|
||||
));
|
||||
results.extend(bench_continuous_scroll::<PreadReaderSequential>(
|
||||
"pread",
|
||||
"sequential",
|
||||
config,
|
||||
));
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
fn bench_single_frame<B: FileReaderBackend>(
|
||||
backend: &str,
|
||||
variant: &str,
|
||||
config: &BenchConfig,
|
||||
) -> Vec<BenchmarkResult> {
|
||||
let path = &config.test_file;
|
||||
let reader = B::open(path).expect("Failed to open file");
|
||||
let total = reader.total_lines();
|
||||
let mut results = Vec::new();
|
||||
|
||||
let positions = [
|
||||
("head", 0),
|
||||
("middle", total / 2),
|
||||
("tail", total.saturating_sub(35)),
|
||||
];
|
||||
|
||||
for (pos_name, start_line) in positions {
|
||||
let mut latencies = Vec::with_capacity(35);
|
||||
for i in 0..35 {
|
||||
let line_idx = start_line + i;
|
||||
if line_idx >= total {
|
||||
break;
|
||||
}
|
||||
let t = std::time::Instant::now();
|
||||
let _ = reader.get_line(line_idx);
|
||||
latencies.push(t.elapsed().as_micros() as u64);
|
||||
}
|
||||
|
||||
let rss = MetricsCollector::read_rss();
|
||||
let faults = MetricsCollector::read_page_faults();
|
||||
|
||||
results.push(BenchmarkResult {
|
||||
category: "render".into(),
|
||||
test_name: format!("single_frame_{pos_name}"),
|
||||
backend: backend.into(),
|
||||
variant: variant.into(),
|
||||
latency_us: latencies,
|
||||
rss_kb: rss.vm_rss_kb,
|
||||
rss_peak_kb: rss.vm_hwm_kb,
|
||||
page_faults: faults.minor_faults + faults.major_faults,
|
||||
extra: HashMap::new(),
|
||||
});
|
||||
}
|
||||
|
||||
reader.close();
|
||||
results
|
||||
}
|
||||
|
||||
fn bench_continuous_scroll<B: FileReaderBackend>(
|
||||
backend: &str,
|
||||
variant: &str,
|
||||
config: &BenchConfig,
|
||||
) -> Vec<BenchmarkResult> {
|
||||
let path = &config.test_file;
|
||||
let reader = B::open(path).expect("Failed to open file");
|
||||
let total = reader.total_lines();
|
||||
|
||||
let iterations = if config.quick_mode { 100 } else { 1000 };
|
||||
let timeout = if config.quick_mode {
|
||||
std::time::Duration::from_secs(1)
|
||||
} else {
|
||||
std::time::Duration::from_secs(10)
|
||||
};
|
||||
|
||||
let mut latencies = Vec::with_capacity(iterations);
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
for i in 0..iterations {
|
||||
if start.elapsed() > timeout {
|
||||
break;
|
||||
}
|
||||
let line_idx = i % total.max(1);
|
||||
let t = std::time::Instant::now();
|
||||
let _ = reader.get_line(line_idx);
|
||||
latencies.push(t.elapsed().as_micros() as u64);
|
||||
}
|
||||
|
||||
let rss = MetricsCollector::read_rss();
|
||||
let faults = MetricsCollector::read_page_faults();
|
||||
|
||||
let mut extra = HashMap::new();
|
||||
extra.insert("total_lines_scrolled".into(), latencies.len() as f64);
|
||||
|
||||
reader.close();
|
||||
|
||||
vec![BenchmarkResult {
|
||||
category: "render".into(),
|
||||
test_name: "continuous_scroll".into(),
|
||||
backend: backend.into(),
|
||||
variant: variant.into(),
|
||||
latency_us: latencies,
|
||||
rss_kb: rss.vm_rss_kb,
|
||||
rss_peak_kb: rss.vm_hwm_kb,
|
||||
page_faults: faults.minor_faults + faults.major_faults,
|
||||
extra,
|
||||
}]
|
||||
}
|
||||
180
crates/bench/src/suites/rotation.rs
Normal file
180
crates/bench/src/suites/rotation.rs
Normal file
@@ -0,0 +1,180 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::data_gen;
|
||||
use crate::metrics::MetricsCollector;
|
||||
use crate::mmap_reader::{self, MmapReaderPlain};
|
||||
use crate::pread_reader::PreadReaderPlain;
|
||||
use crate::runner::BenchConfig;
|
||||
use crate::types::BenchmarkResult;
|
||||
use crate::FileReaderBackend;
|
||||
|
||||
pub fn run(config: &BenchConfig) -> Vec<BenchmarkResult> {
|
||||
let mut results = Vec::new();
|
||||
|
||||
let dir = tempfile::tempdir().expect("Failed to create temp dir");
|
||||
results.extend(bench_truncate_safety_mmap(config, dir.path()));
|
||||
results.extend(bench_truncate_safety_pread(config, dir.path()));
|
||||
results.extend(bench_rotation_detection(config, dir.path()));
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
fn bench_truncate_safety_mmap(
|
||||
_config: &BenchConfig,
|
||||
dir: &std::path::Path,
|
||||
) -> Vec<BenchmarkResult> {
|
||||
let sub_dir = dir.join("trunc_mmap");
|
||||
let path = data_gen::generate_growable_file(&sub_dir).expect("Failed to create file");
|
||||
let iterations: usize = if _config.quick_mode { 3 } else { 10 };
|
||||
|
||||
let mut latencies = Vec::with_capacity(iterations);
|
||||
let mut sigbus_detected = 0usize;
|
||||
|
||||
for _ in 0..iterations {
|
||||
mmap_reader::reset_sigbus_flag();
|
||||
|
||||
let reader = MmapReaderPlain::open(&path).expect("Failed to open file");
|
||||
let original_size = reader.file_size();
|
||||
|
||||
let truncate_size = original_size / 2;
|
||||
data_gen::truncate_file(&path, truncate_size).expect("Failed to truncate");
|
||||
|
||||
let t = std::time::Instant::now();
|
||||
let mid_offset = original_size as u64 / 2;
|
||||
let _ = reader.read_range(mid_offset, 64);
|
||||
latencies.push(t.elapsed().as_micros() as u64);
|
||||
|
||||
if mmap_reader::sigbus_flag() {
|
||||
sigbus_detected += 1;
|
||||
}
|
||||
reader.close();
|
||||
|
||||
let mut f = std::fs::File::create(&path).expect("Failed to recreate file");
|
||||
use std::io::Write;
|
||||
for i in 0..1000u64 {
|
||||
writeln!(f, "restored line {i}").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
let rss = MetricsCollector::read_rss();
|
||||
let faults = MetricsCollector::read_page_faults();
|
||||
|
||||
let mut extra = HashMap::new();
|
||||
extra.insert("iterations".into(), iterations as f64);
|
||||
extra.insert("sigbus_detected".into(), sigbus_detected as f64);
|
||||
extra.insert("crashed".into(), 0.0);
|
||||
|
||||
vec![BenchmarkResult {
|
||||
category: "rotation".into(),
|
||||
test_name: "truncate_safety_mmap".into(),
|
||||
backend: "mmap".into(),
|
||||
variant: "plain".into(),
|
||||
latency_us: latencies,
|
||||
rss_kb: rss.vm_rss_kb,
|
||||
rss_peak_kb: rss.vm_hwm_kb,
|
||||
page_faults: faults.minor_faults + faults.major_faults,
|
||||
extra,
|
||||
}]
|
||||
}
|
||||
|
||||
fn bench_truncate_safety_pread(
|
||||
_config: &BenchConfig,
|
||||
dir: &std::path::Path,
|
||||
) -> Vec<BenchmarkResult> {
|
||||
let sub_dir = dir.join("trunc_pread");
|
||||
let path = data_gen::generate_growable_file(&sub_dir).expect("Failed to create file");
|
||||
let iterations: usize = if _config.quick_mode { 3 } else { 10 };
|
||||
|
||||
let mut latencies = Vec::with_capacity(iterations);
|
||||
let mut error_count = 0usize;
|
||||
|
||||
for _ in 0..iterations {
|
||||
let reader = PreadReaderPlain::open(&path).expect("Failed to open file");
|
||||
let original_size = reader.file_size();
|
||||
|
||||
let truncate_size = original_size / 2;
|
||||
data_gen::truncate_file(&path, truncate_size).expect("Failed to truncate");
|
||||
|
||||
let t = std::time::Instant::now();
|
||||
let mid_offset = original_size as u64 / 2;
|
||||
let result = reader.read_range(mid_offset, 64);
|
||||
latencies.push(t.elapsed().as_micros() as u64);
|
||||
|
||||
if result.is_none() {
|
||||
error_count += 1;
|
||||
}
|
||||
reader.close();
|
||||
|
||||
let mut f = std::fs::File::create(&path).expect("Failed to recreate file");
|
||||
use std::io::Write;
|
||||
for i in 0..1000u64 {
|
||||
writeln!(f, "restored line {i}").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
let rss = MetricsCollector::read_rss();
|
||||
let faults = MetricsCollector::read_page_faults();
|
||||
|
||||
let mut extra = HashMap::new();
|
||||
extra.insert("iterations".into(), iterations as f64);
|
||||
extra.insert("errors_returned".into(), error_count as f64);
|
||||
extra.insert("crashed".into(), 0.0);
|
||||
|
||||
vec![BenchmarkResult {
|
||||
category: "rotation".into(),
|
||||
test_name: "truncate_safety_pread".into(),
|
||||
backend: "pread".into(),
|
||||
variant: "plain".into(),
|
||||
latency_us: latencies,
|
||||
rss_kb: rss.vm_rss_kb,
|
||||
rss_peak_kb: rss.vm_hwm_kb,
|
||||
page_faults: faults.minor_faults + faults.major_faults,
|
||||
extra,
|
||||
}]
|
||||
}
|
||||
|
||||
fn bench_rotation_detection(_config: &BenchConfig, dir: &std::path::Path) -> Vec<BenchmarkResult> {
|
||||
let sub_dir = dir.join("rotation_detect");
|
||||
let iterations: usize = if _config.quick_mode { 3 } else { 10 };
|
||||
|
||||
let mut latencies = Vec::with_capacity(iterations);
|
||||
let mut detected_count = 0usize;
|
||||
|
||||
for _ in 0..iterations {
|
||||
let path = data_gen::generate_growable_file(&sub_dir).expect("Failed to create file");
|
||||
let original_inode = MetricsCollector::get_inode(&path).expect("Failed to get inode");
|
||||
|
||||
let _rotated = data_gen::rotate_file(&path).expect("Failed to rotate file");
|
||||
|
||||
let t = std::time::Instant::now();
|
||||
let detected = MetricsCollector::detect_rotation(original_inode, &path);
|
||||
latencies.push(t.elapsed().as_micros() as u64);
|
||||
|
||||
if detected {
|
||||
detected_count += 1;
|
||||
}
|
||||
|
||||
let _ = std::fs::remove_file(&path);
|
||||
let rotated = sub_dir.join("growable.log.1");
|
||||
let _ = std::fs::remove_file(&rotated);
|
||||
}
|
||||
|
||||
let rss = MetricsCollector::read_rss();
|
||||
let faults = MetricsCollector::read_page_faults();
|
||||
|
||||
let mut extra = HashMap::new();
|
||||
extra.insert("iterations".into(), iterations as f64);
|
||||
extra.insert("rotations_detected".into(), detected_count as f64);
|
||||
|
||||
vec![BenchmarkResult {
|
||||
category: "rotation".into(),
|
||||
test_name: "rotation_detection".into(),
|
||||
backend: "both".into(),
|
||||
variant: "plain".into(),
|
||||
latency_us: latencies,
|
||||
rss_kb: rss.vm_rss_kb,
|
||||
rss_peak_kb: rss.vm_hwm_kb,
|
||||
page_faults: faults.minor_faults + faults.major_faults,
|
||||
extra,
|
||||
}]
|
||||
}
|
||||
129
crates/bench/src/suites/startup.rs
Normal file
129
crates/bench/src/suites/startup.rs
Normal file
@@ -0,0 +1,129 @@
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
|
||||
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_hot_open::<MmapReaderPlain>("mmap", "plain", config));
|
||||
results.extend(bench_hot_open::<MmapReaderSequential>(
|
||||
"mmap",
|
||||
"sequential",
|
||||
config,
|
||||
));
|
||||
results.extend(bench_hot_open::<MmapReaderRandom>("mmap", "random", config));
|
||||
results.extend(bench_hot_open::<MmapReaderPopulate>(
|
||||
"mmap", "populate", config,
|
||||
));
|
||||
results.extend(bench_hot_open::<MmapReaderPhaseAware>(
|
||||
"mmap",
|
||||
"phase_aware",
|
||||
config,
|
||||
));
|
||||
results.extend(bench_hot_open::<PreadReaderPlain>("pread", "plain", config));
|
||||
results.extend(bench_hot_open::<PreadReaderRandom>(
|
||||
"pread", "random", config,
|
||||
));
|
||||
results.extend(bench_hot_open::<PreadReaderSequential>(
|
||||
"pread",
|
||||
"sequential",
|
||||
config,
|
||||
));
|
||||
|
||||
if !config.quick_mode {
|
||||
if MetricsCollector::clear_file_cache(&config.test_file).is_ok() {
|
||||
results.extend(bench_cold_open::<MmapReaderPlain>("mmap", "plain", config));
|
||||
results.extend(bench_cold_open::<MmapReaderSequential>(
|
||||
"mmap",
|
||||
"sequential",
|
||||
config,
|
||||
));
|
||||
results.extend(bench_cold_open::<MmapReaderRandom>(
|
||||
"mmap", "random", config,
|
||||
));
|
||||
results.extend(bench_cold_open::<MmapReaderPopulate>(
|
||||
"mmap", "populate", config,
|
||||
));
|
||||
results.extend(bench_cold_open::<MmapReaderPhaseAware>(
|
||||
"mmap",
|
||||
"phase_aware",
|
||||
config,
|
||||
));
|
||||
results.extend(bench_cold_open::<PreadReaderPlain>(
|
||||
"pread", "plain", config,
|
||||
));
|
||||
results.extend(bench_cold_open::<PreadReaderRandom>(
|
||||
"pread", "random", config,
|
||||
));
|
||||
results.extend(bench_cold_open::<PreadReaderSequential>(
|
||||
"pread",
|
||||
"sequential",
|
||||
config,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
fn bench_hot_open<B: FileReaderBackend>(
|
||||
backend: &str,
|
||||
variant: &str,
|
||||
config: &BenchConfig,
|
||||
) -> Vec<BenchmarkResult> {
|
||||
open_and_measure::<B>(backend, variant, &config.test_file, "hot_open")
|
||||
}
|
||||
|
||||
fn bench_cold_open<B: FileReaderBackend>(
|
||||
backend: &str,
|
||||
variant: &str,
|
||||
config: &BenchConfig,
|
||||
) -> Vec<BenchmarkResult> {
|
||||
let _ = MetricsCollector::clear_file_cache(&config.test_file);
|
||||
open_and_measure::<B>(backend, variant, &config.test_file, "cold_open")
|
||||
}
|
||||
|
||||
fn open_and_measure<B: FileReaderBackend>(
|
||||
backend: &str,
|
||||
variant: &str,
|
||||
path: &Path,
|
||||
test_name: &str,
|
||||
) -> Vec<BenchmarkResult> {
|
||||
let start = std::time::Instant::now();
|
||||
let reader = B::open(path).expect("Failed to open file");
|
||||
let elapsed = start.elapsed();
|
||||
|
||||
let rss = MetricsCollector::read_rss();
|
||||
let faults = MetricsCollector::read_page_faults();
|
||||
|
||||
let result = BenchmarkResult {
|
||||
category: "startup".into(),
|
||||
test_name: test_name.into(),
|
||||
backend: backend.into(),
|
||||
variant: variant.into(),
|
||||
latency_us: vec![elapsed.as_micros() as u64],
|
||||
rss_kb: rss.vm_rss_kb,
|
||||
rss_peak_kb: rss.vm_hwm_kb,
|
||||
page_faults: faults.minor_faults + faults.major_faults,
|
||||
extra: {
|
||||
let mut m = HashMap::new();
|
||||
m.insert("total_lines".into(), reader.total_lines() as f64);
|
||||
m.insert(
|
||||
"file_size_mb".into(),
|
||||
reader.file_size() as f64 / (1024.0 * 1024.0),
|
||||
);
|
||||
m
|
||||
},
|
||||
};
|
||||
reader.close();
|
||||
vec![result]
|
||||
}
|
||||
Reference in New Issue
Block a user