diff --git a/crates/horizon-client/src/packs/cache.rs b/crates/horizon-client/src/packs/cache.rs index 6931a38716..1f17c018b7 100644 --- a/crates/horizon-client/src/packs/cache.rs +++ b/crates/horizon-client/src/packs/cache.rs @@ -756,185 +756,6 @@ mod tests { assert!(lock_path(&out).exists()); } - #[test] - #[ignore = "reproducible overlay benchmark; run with --ignored --nocapture"] - fn benchmark_overlay_reuse() { - use std::{hint::black_box, time::Instant}; - let f = Fixture::new(); - let pack = f.dir_pack("pack"); - let mut files = vec![( - "pack.mcmeta".to_owned(), - fs::read(f.0.join("pack/pack.mcmeta")).unwrap(), - )]; - // 2,048 overrides (32 MiB), 32 merged language files, 8,192 live - // vanilla textures (128 MiB). Synthetic incompressible payloads approximate - // already-compressed PNG bytes; texture decoding is outside this benchmark. - let mut random = 0x1234_5678u32; - let texture: Vec = (0..16384) - .map(|_| { - random ^= random << 13; - random ^= random >> 17; - random ^= random << 5; - (random >> 24) as u8 - }) - .collect(); - for i in 0..8192 { - let name = format!("assets/minecraft/textures/block/{i}.png"); - f.write(&format!("vanilla/{name}"), &texture); - if i < 2048 { - f.write(&format!("pack/{name}"), &texture); - files.push((name, texture.clone())); - } - } - for i in 0..32 { - let name = format!("assets/minecraft/lang/{i}.json"); - let vanilla = serde_json::to_vec(&serde_json::Value::Object( - (0..2000) - .map(|j| { - ( - format!("key{j}"), - serde_json::json!(format!("Vanilla translation {j}")), - ) - }) - .collect(), - )) - .unwrap(); - let body = serde_json::to_vec(&serde_json::Value::Object( - (0..1000) - .map(|j| { - ( - format!("key{j}"), - serde_json::json!(format!("Pack translation {j}")), - ) - }) - .collect(), - )) - .unwrap(); - f.write(&format!("vanilla/{name}"), &vanilla); - f.write(&format!("pack/{name}"), &body); - files.push((name, body)); - } - let borrowed: Vec<_> = files - .iter() - .map(|(name, body)| (name.as_str(), body.clone())) - .collect(); - f.write("pack.zip", &archive(&borrowed, true)); - println!( - "fixture ZIP bytes={}", - fs::metadata(f.0.join("pack.zip")).unwrap().len() - ); - let zip = packs::pack( - "zip".into(), - Text::empty(), - Source::Zip(f.0.join("pack.zip")), - ) - .unwrap(); - let median = |mut times: Vec| { - times.sort_by(f64::total_cmp); - times[times.len() / 2] - }; - for (kind, pack) in [("directory", pack), ("deflated zip", zip)] { - f.rebuild_matches(std::slice::from_ref(&pack)); - let vanilla = f.0.join("vanilla"); - let out = f.0.join("cached"); - let mut baseline = Vec::new(); - let mut cold = Vec::new(); - let mut hit = Vec::new(); - for _ in 0..11 { - let start = Instant::now(); - build_overlay_uncached( - black_box(&vanilla), - black_box(std::slice::from_ref(&pack)), - black_box(&f.0.join("reference")), - &mut Dependencies::default(), - ) - .unwrap(); - baseline.push(start.elapsed().as_secs_f64() * 1000.0); - fs::remove_file(marker_path(&out)).unwrap(); - let start = Instant::now(); - packs::build_overlay( - black_box(&vanilla), - black_box(std::slice::from_ref(&pack)), - black_box(&out), - ) - .unwrap(); - cold.push(start.elapsed().as_secs_f64() * 1000.0); - let start = Instant::now(); - packs::build_overlay( - black_box(&vanilla), - black_box(std::slice::from_ref(&pack)), - black_box(&out), - ) - .unwrap(); - hit.push(start.elapsed().as_secs_f64() * 1000.0); - assert_eq!( - output_signature(&out).unwrap(), - output_signature(&f.0.join("reference")).unwrap() - ); - } - println!( - "{kind}: baseline={:.3} ms cold={:.3} ms unchanged={:.3} ms (11 medians, full validation)", - median(baseline), - median(cold), - median(hit) - ); - } - } - - #[test] - #[ignore = "content-heavy few-file directory benchmark"] - fn benchmark_content_heavy_directory() { - use std::{hint::black_box, time::Instant}; - let f = Fixture::new(); - f.write("vanilla/facts.json", b"[]"); - let pack = f.dir_pack("pack"); - let bytes = vec![42; 8 * 1024 * 1024]; - for i in 0..4 { - f.write(&format!("pack/assets/minecraft/textures/{i}.png"), &bytes); - } - let vanilla = f.0.join("vanilla"); - let out = f.0.join("cached"); - f.rebuild_matches(std::slice::from_ref(&pack)); - let mut timings = [Vec::new(), Vec::new(), Vec::new()]; - for _ in 0..11 { - let start = Instant::now(); - build_overlay_uncached( - black_box(&vanilla), - black_box(std::slice::from_ref(&pack)), - &f.0.join("reference"), - &mut Dependencies::default(), - ) - .unwrap(); - timings[0].push(start.elapsed().as_secs_f64() * 1000.0); - fs::remove_file(marker_path(&out)).unwrap(); - for times in &mut timings[1..] { - let start = Instant::now(); - packs::build_overlay( - black_box(&vanilla), - black_box(std::slice::from_ref(&pack)), - black_box(&out), - ) - .unwrap(); - times.push(start.elapsed().as_secs_f64() * 1000.0); - } - assert_eq!( - output_signature(&out).unwrap(), - output_signature(&f.0.join("reference")).unwrap() - ); - } - let medians: Vec<_> = timings - .into_iter() - .map(|mut times| { - times.sort_by(f64::total_cmp); - times[5] - }) - .collect(); - println!( - "4 x 8 MiB directory textures: baseline={:.3} ms cold={:.3} ms unchanged={:.3} ms (11 medians, full validation)", - medians[0], medians[1], medians[2] - ); - } - #[cfg(unix)] #[test] fn relative_builder_targets_are_preserved() { diff --git a/docs/reuse-pack-overlays.md b/docs/reuse-pack-overlays.md index 2a52355534..53e8bbc3a2 100644 --- a/docs/reuse-pack-overlays.md +++ b/docs/reuse-pack-overlays.md @@ -20,20 +20,17 @@ Linux x86_64/ext4/NVMe, Ryzen 5 5600X, Rust 1.99.0, Cargo `fast`, measured 2026- Unchanged speedups are approximately 226x, 359x and 775x. Cold overhead on these ZIPs is 0.1–2.6%, replacing the original cache implementation's 73–79% synthetic overhead. This measures overlay assembly and cache validation, excluding scan, texture decoding, total startup and FPS. +Mac mini (M4 Pro, APFS, Rust stable, Cargo `fast`), same harness and packs, measured 2026-10-15 during review while the machine ran other builds: + +| ZIP pack | Original rebuild | Cached cold | Unchanged hit | Saved per hit | +| --- | ---: | ---: | ---: | ---: | +| Faithful 32x, 26.3 September 2026 | 1,184.400 ms | 1,295.069 ms | 0.571 ms | 1,183.829 ms | +| FPBR, 1.21.9 R1.3 | 5,292.149 ms | 5,503.965 ms | 0.391 ms | 5,291.758 ms | + +Creating thousands of files and links costs far more on macOS than on Linux, so the saving there is seconds per startup with a pack enabled. + Pack downloads were verified against publisher SHA-512 metadata. Exact versions, URLs, sizes, asset counts, hashes and prepared-manifest identity are recorded in `benchmarks/overlay-real-packs-2026-10-15.json`. Sources: [Faithful](https://modrinth.com/resourcepack/faithful-32x/version/lDYpMiqk), [Default HD](https://modrinth.com/resourcepack/default-hd-128x/version/rErHu3BE), [FPBR](https://modrinth.com/resourcepack/fpbr/version/dynf82xk). FPBR targets older 1.21.9 snapshots; it is measured as a large real 128x archive over prepared 26.3 resources, without claiming visual/version compatibility. Default HD is the publisher's free demo. Downloaded pack contents are not committed. -## Synthetic coverage - -The existing fixtures contain 8,192 vanilla textures (128 MiB), 2,048 overrides (32 MiB) and 32 merged language files; equivalent directory/ZIP payloads are incompressible bytes. Eleven median samples compare identical output outside timing: - -| Fixture | Original | Cached cold | Hit | -| --- | ---: | ---: | ---: | -| Directory | 108.487 ms | 126.438 ms | 7.438 ms | -| Deflated ZIP, 32.45 MiB | 155.540 ms | 155.453 ms | 0.128 ms | -| Directory, four 8 MiB files | 0.115 ms | 0.271 ms | 0.076 ms | - -Directory topology scans remain a cost: the larger directory fixture adds about 17% cold overhead and gains 14.6x on hits. Tiny cold overlays add about 0.16 ms. No overall startup gain is inferred from either table. - ## Reproduce and validate ```sh @@ -41,10 +38,9 @@ export CARGO_INCREMENTAL=0 export HORIZON_OVERLAY_BENCH_VANILLA=/path/to/prepared/26.3 export HORIZON_OVERLAY_BENCH_PACKS='["/path/to/Faithful.zip","/path/to/DefaultHD.zip","/path/to/FPBR.zip"]' cargo test -p horizon-client --lib --profile fast packs::cache::tests::benchmark_real_resource_root -- --ignored --nocapture --test-threads=1 -cargo test -p horizon-client --lib --profile fast packs::cache::tests::benchmark -- --ignored --nocapture --test-threads=1 --skip benchmark_real_resource_root cargo test -p horizon-client --lib --profile fast packs:: -- --nocapture cargo clippy -p horizon-client --lib --tests --profile fast -- -D warnings cargo fmt --all -- --check ``` -Thirteen focused tests pass, covering source edits/replacements, Unix preserved-mtime edits, pack order/removal, overlays/filters, vanilla membership, marker/output-root recovery, failed ZIP rebuilds, symlink aliases/cycles, contention and unchanged relative link targets. All three opt-in benchmarks pass; strict Clippy, formatting and whitespace checks pass. Each overlay retains a small marker and empty lock file; cache hits retain no archive byte buffer. +Thirteen focused tests pass, covering source edits/replacements, Unix preserved-mtime edits, pack order/removal, overlays/filters, vanilla membership, marker/output-root recovery, failed ZIP rebuilds, symlink aliases/cycles, contention and unchanged relative link targets. The opt-in real-pack benchmark passes; strict Clippy, formatting and whitespace checks pass. Each overlay retains a small marker and empty lock file; cache hits retain no archive byte buffer.