Skip to content

Commit 0bf4b2f

Browse files
zackeesclaude
andauthored
feat(library-selection): #205 Phase 7 perf benchmarks (P-02, P-03) (#210)
Adds two `criterion` benchmarks that capture baseline numbers for issue #205's Phase 7 perf gates. This PR lands the harness; CI gating against thresholds is a follow-up once we have a stable measurement on a known runner spec. ## Benchmarks - `crates/fbuild-header-scan/benches/scan_throughput.rs` Measures `scan()` throughput on three input sizes (tiny/medium/large) with adversary fixtures (raw strings, comments, identifiers ending in `R`/`L`, char literals). Target per #205 P-03: >= 50 MB/s single-thread. - `crates/fbuild-library-select/benches/resolve_cold.rs` End-to-end `resolve()` walk on a synthetic 30-library framework tree with a 5-level transitive include chain. Target per #205 P-02: <= 200 ms cold for a typical teensy41 project. Uses `MiniFramework` from `fbuild-test-support` so the bench is hermetic. Both use `harness = false` and depend on the new workspace `criterion = "0.5"` dev-dep. Run with: ```bash uv run soldr cargo bench -p fbuild-header-scan --bench scan_throughput uv run soldr cargo bench -p fbuild-library-select --bench resolve_cold ``` ## Out of scope (still tracked) - P-01 (warm matrix) — gated on Phase 4 cache (zccache#130). - P-04 (cache-hit round-trip) — same. - CI gating against the captured thresholds — follow-up once runner variance is characterized. ## Verification - `uv run soldr cargo build --release -p fbuild-header-scan -p fbuild-library-select --benches` — green. - `uv run soldr cargo clippy --workspace --all-targets -- -D warnings` — green. - `uv run soldr cargo fmt --all --check` — clean. Refs: #205, #202, #204 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent a28ead6 commit 0bf4b2f

8 files changed

Lines changed: 341 additions & 0 deletions

File tree

Cargo.lock

Lines changed: 135 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ tokio-tungstenite = "0.24"
5858
async-trait = "0.1"
5959
dashmap = "6"
6060
blake3 = "1"
61+
criterion = { version = "0.5", default-features = false, features = ["html_reports"] }
6162
mimalloc = "0.1"
6263
object = { version = "0.36", default-features = false, features = ["read", "std", "elf", "write"] }
6364
rusqlite = { version = "0.31", features = ["bundled"] }

crates/fbuild-header-scan/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,9 @@ license.workspace = true
99
[dependencies]
1010

1111
[dev-dependencies]
12+
criterion = { workspace = true }
1213
tempfile = { workspace = true }
14+
15+
[[bench]]
16+
name = "scan_throughput"
17+
harness = false
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# benches
2+
3+
Criterion micro-benchmarks for `fbuild-header-scan`.
4+
5+
`scan_throughput.rs` measures `scan()` single-thread throughput (MB/s) over
6+
three synthetic C++ fixtures: **tiny** (~64 B, per-call overhead),
7+
**medium** (100 KB), and **large** (2 MB, stand-in for a Teensy-core-sized
8+
translation unit). The fixtures exercise the scanner's adversary paths
9+
(comments, string / raw-string literals containing fake `#include`s,
10+
identifiers ending in `R` / `L`).
11+
12+
Per FastLED/fbuild#205 P-03 the aspirational threshold is **≥ 50 MB/s
13+
single-thread**. This bench captures the baseline; it is not yet a CI
14+
gate (Phase 7 will wire that up in a follow-up).
15+
16+
Run:
17+
18+
```bash
19+
uv run soldr cargo bench -p fbuild-header-scan --bench scan_throughput
20+
```
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//! Criterion benchmark for `fbuild_header_scan::scan` throughput.
2+
//!
3+
//! P-03 of FastLED/fbuild#205: capture single-thread MB/s on three input
4+
//! sizes (tiny / medium / large) so future PRs can regress against a
5+
//! recorded baseline. The aspirational threshold is ≥ 50 MB/s
6+
//! single-thread; this harness records the number but does not gate CI.
7+
8+
use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput};
9+
use fbuild_header_scan::scan;
10+
11+
/// Generate a synthetic C++ source string at least `target_bytes` long.
12+
///
13+
/// The template intentionally exercises the scanner's adversary paths:
14+
/// angled + quoted `#include`, line and multi-line block comments
15+
/// containing fake `#include`s, string and raw-string literals with
16+
/// embedded `#include` payloads, identifiers ending in `R` / `L`
17+
/// (which must NOT be treated as raw-string prefixes), and a char
18+
/// literal containing `#`. Repeated until we hit the byte budget.
19+
fn fixture(target_bytes: usize) -> String {
20+
let template = "\
21+
#include <a.h>\n\
22+
// comment with #include <not_real.h>\n\
23+
const char* s = \"#include <also_not_real.h>\";\n\
24+
const char* r = R\"(#include <not_real_either.h>)\";\n\
25+
auto FooR = 0; // identifier ending in R, NOT a raw string\n\
26+
auto FooL = 1; // identifier ending in L, NOT a wide-string prefix\n\
27+
/* block\n #include <inside_block.h>\n*/\n\
28+
char c = '#';\n\
29+
#include \"b.h\"\n\
30+
";
31+
let mut s = String::with_capacity(target_bytes + template.len());
32+
while s.len() < target_bytes {
33+
s.push_str(template);
34+
}
35+
s
36+
}
37+
38+
fn bench_scanner(c: &mut Criterion) {
39+
let mut group = c.benchmark_group("scan");
40+
for (name, size) in [
41+
("tiny", 64usize),
42+
("medium", 100 * 1024),
43+
("large", 2 * 1024 * 1024),
44+
] {
45+
let src = fixture(size);
46+
let actual_len = src.len();
47+
group.throughput(Throughput::Bytes(actual_len as u64));
48+
group.bench_function(name, |b| {
49+
b.iter(|| {
50+
let refs = scan(black_box(&src));
51+
black_box(refs);
52+
});
53+
});
54+
}
55+
group.finish();
56+
}
57+
58+
criterion_group!(benches, bench_scanner);
59+
criterion_main!(benches);

crates/fbuild-library-select/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,9 @@ walkdir = { workspace = true }
1414

1515
[dev-dependencies]
1616
tempfile = { workspace = true }
17+
criterion = { workspace = true }
18+
fbuild-test-support = { path = "../fbuild-test-support" }
19+
20+
[[bench]]
21+
name = "resolve_cold"
22+
harness = false
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# fbuild-library-select benches
2+
3+
Criterion benchmarks for the PlatformIO-LDF-style library resolver.
4+
5+
## resolve_cold
6+
7+
End-to-end cold-path measurement of `resolve()` against a synthetic
8+
~30-library framework tree (Teensyduino-class) built with `MiniFramework`. A
9+
5-deep transitive include chain forces the two-pass LDF reconciliation; the
10+
remaining libraries are unreferenced and must be rejected — that doubles as a
11+
guard against the #204 over-selection regression. Walks the tempdir on every
12+
iteration since no cache sits in front of `resolve()` today (Phase 4
13+
memoization waits on zccache#130).
14+
15+
The Phase 7 P-02 threshold from FastLED/fbuild#205 is **≤ 200 ms cold for a
16+
typical teensy41 project**. This bench captures the baseline; future PRs gate
17+
against it.
18+
19+
Run:
20+
21+
```bash
22+
uv run soldr cargo bench -p fbuild-library-select --bench resolve_cold
23+
```

0 commit comments

Comments
 (0)