//! Round-15 CPU/alloc micro-pack (no Postgres). //! //! Each section is BEFORE (verbatim replica of the shipped-before shape) vs //! AFTER (the shipped-after shape, or the shipped function itself), with an //! equivalence gate and a `GATE FAIL … rollback` check that exits non-zero if //! the AFTER arm fails to beat its BEFORE — the round's roll-back rule encoded //! into the benchmark. //! //! [B1] exif Make/Model — `display_value().to_string().trim_matches('"') //! .trim().to_string()` allocates the display String, then throws it away //! to allocate the trimmed copy (2 allocs). The shipped //! `exif_service::display_value_trimmed` trims in place on the owned //! buffer (`drain` + `truncate`) — 1 alloc. Per ingested photo. //! [B2] content-index worker `supports()` — `text_extractor::supports` //! (lowercases the MIME + extension, 1–2 allocs) was called TWICE per //! file per drain batch: once in the wanted-hashes filter, once in the //! records loop. The shipped code classifies each file once into a //! `Vec` and threads it through both. Per reseed batch (every //! file in the library). //! //! Run: //! cargo run --release --features bench --example bench_round15_micro //! Tunables (env): BENCH_ITERS (200000), BENCH_BATCH (256) use std::alloc::{GlobalAlloc, Layout, System}; use std::env; use std::hint::black_box; use std::sync::atomic::{AtomicU64, Ordering}; use std::time::Instant; use oxicloud::infrastructure::services::search_index::text_extractor; static ALLOC_CALLS: AtomicU64 = AtomicU64::new(0); struct CountingAlloc; unsafe impl GlobalAlloc for CountingAlloc { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { ALLOC_CALLS.fetch_add(1, Ordering::Relaxed); unsafe { System.alloc(layout) } } unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { unsafe { System.dealloc(ptr, layout) } } unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { ALLOC_CALLS.fetch_add(1, Ordering::Relaxed); unsafe { System.realloc(ptr, layout, new_size) } } unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { ALLOC_CALLS.fetch_add(1, Ordering::Relaxed); unsafe { System.alloc_zeroed(layout) } } } #[global_allocator] static GLOBAL: CountingAlloc = CountingAlloc; fn env_or(key: &str, default: T) -> T { env::var(key) .ok() .and_then(|v| v.parse().ok()) .unwrap_or(default) } struct Measured { wall_ns_per_op: f64, allocs_per_op: f64, } fn measure(iters: usize, mut f: F) -> Measured { let a0 = ALLOC_CALLS.load(Ordering::Relaxed); let t = Instant::now(); for _ in 0..iters { f(); } let wall = t.elapsed().as_nanos() as f64 / iters as f64; let allocs = (ALLOC_CALLS.load(Ordering::Relaxed) - a0) as f64 / iters as f64; Measured { wall_ns_per_op: wall, allocs_per_op: allocs, } } fn print_row(label: &str, m: &Measured) { println!( "| {:<42} | {:>12.1} | {:>10.2} |", label, m.wall_ns_per_op, m.allocs_per_op ); } fn header_footer(name: &str, before: &Measured, after: &Measured) { println!("| arm | ns/op | allocs/op |"); print_row(&format!("BEFORE {name}"), before); print_row(&format!("AFTER {name}"), after); println!( "# {:.2}x wall, {:.2} fewer allocs/op", before.wall_ns_per_op / after.wall_ns_per_op, before.allocs_per_op - after.allocs_per_op ); } // ──────────────────────────────────────────────────────────────────────────── // [B1] exif Make/Model trim — 2 allocs (throwaway display String) vs 1 (in place) // ──────────────────────────────────────────────────────────────────────────── /// BEFORE: the shipped-before chain. `raw` stands in for the field's rendered /// display value; `to_string()` mirrors `display_value().to_string()` (the one /// unavoidable alloc), then `.trim_matches('"').trim().to_string()` allocates a /// second time for the trimmed copy. fn trim_before(raw: &str) -> String { raw.to_string().trim_matches('"').trim().to_string() } /// AFTER: verbatim replica of `exif_service::display_value_trimmed` — trims in /// place on the already-owned buffer, so only the display String is allocated. fn trim_after(raw: &str) -> String { let mut s = raw.to_string(); let trimmed = s.trim_matches('"').trim(); let start = trimmed.as_ptr().addr() - s.as_ptr().addr(); let len = trimmed.len(); s.drain(..start); s.truncate(len); s } fn section_exif_trim() { let iters: usize = env_or("BENCH_ITERS", 200_000); // Representative EXIF Make/Model display values: the widely-seen quoted // form, plus a padded one and an already-clean one. let samples = ["\"Canon\"", "\"NIKON CORPORATION\"", " Apple ", "SONY"]; // Gate: byte-identical output to the old chain across every shape. for s in samples { assert_eq!(trim_before(s), trim_after(s), "trim differs for {s:?}"); } let before = measure(iters, || { for s in samples { black_box(trim_before(black_box(s))); } }); let after = measure(iters, || { for s in samples { black_box(trim_after(black_box(s))); } }); println!("\n## [B1] exif Make/Model trim (4 sample values/op)"); header_footer("exif trim", &before, &after); if after.allocs_per_op >= before.allocs_per_op { eprintln!("GATE FAIL [B1]: in-place trim did not reduce allocations — rollback"); std::process::exit(1); } } // ──────────────────────────────────────────────────────────────────────────── // [B2] content-index worker supports() — 2× per file vs 1× (memoized) // ──────────────────────────────────────────────────────────────────────────── /// One drained file row: (name, mime, size). Mirrors the worker's /// `FileIndexRow` projection (only the fields `supports` + the size gate read). struct FileRow { name: &'static str, mime: &'static str, size: i64, } fn corpus(n: usize) -> Vec { // A realistic reseed mix: text/markdown/pdf/office (supported) interleaved // with images/video/binaries (unsupported — the fast reject). const MIX: &[(&str, &str, i64)] = &[ ("notes.txt", "text/plain", 4_000), ("readme.md", "text/markdown", 8_000), ("report.pdf", "application/pdf", 250_000), ("photo.jpg", "image/jpeg", 3_000_000), ( "sheet.xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", 120_000, ), ("clip.mp4", "video/mp4", 40_000_000), ("data.bin", "application/octet-stream", 1_000), ("page.html", "text/html; charset=utf-8", 20_000), ]; (0..n) .map(|i| { let (name, mime, size) = MIX[i % MIX.len()]; FileRow { name, mime, size } }) .collect() } fn section_supports() { let iters: usize = env_or("BENCH_ITERS", 200_000) / 20; // heavier op let batch: usize = env_or("BENCH_BATCH", 256); let max_bytes: u64 = 10 * 1024 * 1024; let files = corpus(batch); // BEFORE: `supports` is evaluated in the wanted-hashes filter AND again per // file in the records loop — twice per file. let run_before = |files: &[FileRow]| -> (usize, usize) { let wanted = files .iter() .filter(|f| text_extractor::supports(f.name, f.mime) && f.size as u64 <= max_bytes) .count(); let mut supported_files = 0; for f in files { if text_extractor::supports(f.name, f.mime) { supported_files += 1; } } (wanted, supported_files) }; // AFTER: classify each file once into a `Vec`; both the filter and the // records loop read the flag. let run_after = |files: &[FileRow]| -> (usize, usize) { let supported: Vec = files .iter() .map(|f| text_extractor::supports(f.name, f.mime)) .collect(); let wanted = files .iter() .zip(&supported) .filter(|&(f, s)| *s && f.size as u64 <= max_bytes) .count(); let mut supported_files = 0; for (_, &s) in files.iter().zip(&supported) { if s { supported_files += 1; } } (wanted, supported_files) }; // Gate: identical (wanted, supported) tallies. assert_eq!( run_before(&files), run_after(&files), "supports tally differs" ); let before = measure(iters, || { black_box(run_before(black_box(&files))); }); let after = measure(iters, || { black_box(run_after(black_box(&files))); }); println!("\n## [B2] content-index supports() ({batch} files/batch)"); header_footer("supports/batch", &before, &after); if after.allocs_per_op >= before.allocs_per_op || after.wall_ns_per_op >= before.wall_ns_per_op { eprintln!("GATE FAIL [B2]: single-classify did not beat the double call — rollback"); std::process::exit(1); } } fn main() { println!("#################################################################"); println!("# Round-15 CPU/alloc micro-pack"); println!("#################################################################"); section_exif_trim(); section_supports(); println!("\nGATE PASS (all sections)"); }