|
| 1 | +//! P-01 (mini) warm-resolver benchmark. |
| 2 | +//! |
| 3 | +//! Phase 4 of <https://github.com/FastLED/fbuild/issues/205> shipped |
| 4 | +//! [`fbuild_library_select::cache::resolve_cached`], a `KvStore`-backed memo |
| 5 | +//! in front of [`fbuild_library_select::resolve`]. AC#5 / P-01 of #205 sets |
| 6 | +//! a "warm library-selection ≤ current fbuild + 50 ms" goal; the real |
| 7 | +//! per-board matrix lives under `bench/fastled-examples/` and waits on a |
| 8 | +//! checked-out FastLED tree. This bench is the per-crate counterpart: it |
| 9 | +//! measures the synthetic warm path against the same `MiniFramework` |
| 10 | +//! fixture as `resolve_cold`, so we have a regression guard for the cache- |
| 11 | +//! hit code path independent of the larger matrix. |
| 12 | +//! |
| 13 | +//! Structure mirrors `resolve_cold.rs`: build the ~30-library tree once, |
| 14 | +//! open a `KvStore` once, prime the cache with one untimed `resolve_cached` |
| 15 | +//! call, then time only the second invocation (the hit path). |
| 16 | +//! |
| 17 | +//! Run with: |
| 18 | +//! |
| 19 | +//! ```text |
| 20 | +//! uv run soldr cargo bench -p fbuild-library-select --bench resolve_warm |
| 21 | +//! ``` |
| 22 | +//! |
| 23 | +//! Follows up #215 (mini bench) and #205 Phase 7 (perf budgets). |
| 24 | +
|
| 25 | +use std::path::PathBuf; |
| 26 | + |
| 27 | +use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput}; |
| 28 | +use fbuild_library_select::cache::{resolve_cached, CacheKeyInputs}; |
| 29 | +use fbuild_packages::library::framework_library::discover_framework_libraries; |
| 30 | +use fbuild_packages::library::FrameworkLibrary; |
| 31 | +use fbuild_test_support::MiniFramework; |
| 32 | +use zccache_artifact::KvStore; |
| 33 | + |
| 34 | +const LIB_COUNT: usize = 30; |
| 35 | +const CHAIN_LEN: usize = 5; |
| 36 | + |
| 37 | +fn build_fixture() -> ( |
| 38 | + MiniFramework, |
| 39 | + Vec<PathBuf>, |
| 40 | + Vec<PathBuf>, |
| 41 | + Vec<FrameworkLibrary>, |
| 42 | +) { |
| 43 | + let mut mf = MiniFramework::new(); |
| 44 | + for i in 0..LIB_COUNT { |
| 45 | + let name = format!("Lib{i:02}"); |
| 46 | + let next = if i + 1 < CHAIN_LEN { |
| 47 | + Some(format!("Lib{:02}", i + 1)) |
| 48 | + } else { |
| 49 | + None |
| 50 | + }; |
| 51 | + let header = if let Some(n) = &next { |
| 52 | + format!("#pragma once\n#include <{n}.h>\n") |
| 53 | + } else { |
| 54 | + "#pragma once\n".to_string() |
| 55 | + }; |
| 56 | + let cpp = format!("#include <{name}.h>\nvoid {name}_func() {{}}\n"); |
| 57 | + mf.add_library(&name).header(&header).cpp(&cpp).done(); |
| 58 | + } |
| 59 | + mf.sketch("#include <Lib00.h>\nvoid setup() {}\nvoid loop() {}\n"); |
| 60 | + |
| 61 | + let libs = discover_framework_libraries(&mf.libraries_dir()); |
| 62 | + let seeds = mf.project_seeds(); |
| 63 | + let search_paths = mf.project_search_paths(); |
| 64 | + (mf, seeds, search_paths, libs) |
| 65 | +} |
| 66 | + |
| 67 | +fn bench_resolve_warm(c: &mut Criterion) { |
| 68 | + let (mf, seeds, search_paths, libs) = build_fixture(); |
| 69 | + let kv_dir = tempfile::tempdir().expect("resolve_warm: failed to create kv tempdir"); |
| 70 | + let kv = KvStore::open(kv_dir.path().join("kv")).expect("resolve_warm: KvStore::open failed"); |
| 71 | + |
| 72 | + let framework_root = mf.framework_root().to_path_buf(); |
| 73 | + let inputs = CacheKeyInputs { |
| 74 | + toolchain_triple: "avr-unknown-none", |
| 75 | + framework_install_path: &framework_root, |
| 76 | + framework_version: "1.59.0", |
| 77 | + }; |
| 78 | + |
| 79 | + // Prime the cache so the timed loop measures the hit path only. |
| 80 | + let primed = resolve_cached(&seeds, &search_paths, &libs, &inputs, &kv) |
| 81 | + .expect("resolve_warm: prime resolve_cached failed"); |
| 82 | + assert!( |
| 83 | + !primed.from_cache, |
| 84 | + "resolve_warm: priming call must miss (got hit)" |
| 85 | + ); |
| 86 | + |
| 87 | + // WHY: confirm the very next call hits before entering the bench loop; |
| 88 | + // otherwise we'd silently measure cold work and report a misleading |
| 89 | + // baseline. |
| 90 | + let probe = resolve_cached(&seeds, &search_paths, &libs, &inputs, &kv) |
| 91 | + .expect("resolve_warm: probe resolve_cached failed"); |
| 92 | + assert!( |
| 93 | + probe.from_cache, |
| 94 | + "resolve_warm: second call did not hit cache; bench would measure misses" |
| 95 | + ); |
| 96 | + |
| 97 | + let mut group = c.benchmark_group("resolve"); |
| 98 | + group.throughput(Throughput::Elements(libs.len() as u64)); |
| 99 | + group.bench_function("warm_30_libs_chain_5", |b| { |
| 100 | + b.iter(|| { |
| 101 | + let res = resolve_cached( |
| 102 | + black_box(&seeds), |
| 103 | + black_box(&search_paths), |
| 104 | + black_box(&libs), |
| 105 | + black_box(&inputs), |
| 106 | + black_box(&kv), |
| 107 | + ) |
| 108 | + .expect("resolve_warm: resolve_cached failed inside bench loop"); |
| 109 | + if !res.from_cache { |
| 110 | + panic!("resolve_warm: bench iteration missed the cache"); |
| 111 | + } |
| 112 | + black_box(res); |
| 113 | + }); |
| 114 | + }); |
| 115 | + group.finish(); |
| 116 | + drop(mf); |
| 117 | + drop(kv_dir); |
| 118 | +} |
| 119 | + |
| 120 | +criterion_group!(benches, bench_resolve_warm); |
| 121 | +criterion_main!(benches); |
0 commit comments