|
2 | 2 | // Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk> |
3 | 3 | // |
4 | 4 | // benches/git_reticulator_bench.rs |
5 | | -// Criterion benchmarks for git-reticulator. |
| 5 | +// Criterion benchmarks for the git-reticulator lattice engine. |
6 | 6 | // |
7 | | -// Baselines measured: |
8 | | -// B1 - build_lattice with a short repo path and db URI |
9 | | -// B2 - query_lattice with a short zoom node and db URI |
10 | | -// B3 - build_lattice with a long (4 KiB) repo path |
11 | | -// B4 - query_lattice with a long (4 KiB) zoom node |
12 | | -// B5 - Sequential build-then-query pipeline |
| 7 | +// These measure the *real* algorithmic core (PROOF-NEEDS P1/P2/P4) rather than |
| 8 | +// the IO-free `affine` compat shim, so the numbers are behavioural baselines |
| 9 | +// for the algorithms a future AffineScript core must match: |
| 10 | +// engine/condense - Kosaraju SCC condensation, parametric over lattice size |
| 11 | +// engine/zoom - LOD descendant selection on a large containment forest |
| 12 | +// engine/meet - lowest-common-ancestor over deep ancestor chains |
| 13 | +// engine/precedes - node partial-order query (condenses internally per call) |
| 14 | +// engine/ingest - std-only filesystem walk of this repo's own `src/` tree |
13 | 15 |
|
14 | 16 | use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; |
15 | | -use git_reticulator::lattice::affine; |
| 17 | +use git_reticulator::ingest; |
| 18 | +use git_reticulator::lattice::{Lattice, LatticeBuilder, SemanticLevel}; |
16 | 19 |
|
17 | | -// --------------------------------------------------------------------------- |
18 | | -// B1: build_lattice — short inputs |
19 | | -// --------------------------------------------------------------------------- |
20 | | -fn bench_build_lattice_short(c: &mut Criterion) { |
21 | | - c.bench_function("build_lattice/short", |b| { |
22 | | - b.iter(|| { |
23 | | - affine::build_lattice( |
24 | | - black_box("github.com/hyperpolymath/git-reticulator"), |
25 | | - black_box("postgres://localhost:5432/reticulator"), |
26 | | - ) |
27 | | - }) |
28 | | - }); |
| 20 | +/// Build a synthetic lattice of `modules` modules, each with `files_per` files, |
| 21 | +/// each with `defs_per` definitions (Module ⊃ File ⊃ Definition). Definitions |
| 22 | +/// are wired into a forward call-chain with periodic back-edges, so the SCC |
| 23 | +/// condensation has genuine cycles to collapse rather than a trivial DAG. |
| 24 | +fn synthetic(modules: usize, files_per: usize, defs_per: usize) -> Lattice { |
| 25 | + let mut b = LatticeBuilder::new(); |
| 26 | + let mut defs = Vec::new(); |
| 27 | + for mi in 0..modules { |
| 28 | + let m = b.add_keyword( |
| 29 | + format!("mod{mi}"), |
| 30 | + format!("/mod{mi}"), |
| 31 | + SemanticLevel::Module, |
| 32 | + None, |
| 33 | + ); |
| 34 | + for fi in 0..files_per { |
| 35 | + let f = b.add_keyword( |
| 36 | + format!("file{mi}_{fi}"), |
| 37 | + format!("/mod{mi}/file{fi}.rs"), |
| 38 | + SemanticLevel::File, |
| 39 | + Some(m), |
| 40 | + ); |
| 41 | + for di in 0..defs_per { |
| 42 | + let d = b.add_keyword( |
| 43 | + format!("def{mi}_{fi}_{di}"), |
| 44 | + format!("/mod{mi}/file{fi}.rs"), |
| 45 | + SemanticLevel::Definition, |
| 46 | + Some(f), |
| 47 | + ); |
| 48 | + defs.push(d); |
| 49 | + } |
| 50 | + } |
| 51 | + } |
| 52 | + // forward call chain over every definition |
| 53 | + for w in defs.windows(2) { |
| 54 | + b.add_relationship(w[0], w[1], 1.0, "calls".into()); |
| 55 | + } |
| 56 | + // periodic back-edges (every 4th def calls back 3 steps) → real SCCs |
| 57 | + let mut i = 3; |
| 58 | + while i < defs.len() { |
| 59 | + b.add_relationship(defs[i], defs[i - 3], 1.0, "calls".into()); |
| 60 | + i += 4; |
| 61 | + } |
| 62 | + b.build() |
29 | 63 | } |
30 | 64 |
|
31 | | -// --------------------------------------------------------------------------- |
32 | | -// B2: query_lattice — short inputs |
33 | | -// --------------------------------------------------------------------------- |
34 | | -fn bench_query_lattice_short(c: &mut Criterion) { |
35 | | - c.bench_function("query_lattice/short", |b| { |
36 | | - b.iter(|| { |
37 | | - affine::query_lattice( |
38 | | - black_box("module::lattice::affine"), |
39 | | - black_box("postgres://localhost:5432/reticulator"), |
40 | | - ) |
41 | | - }) |
42 | | - }); |
| 65 | +fn bench_condense(c: &mut Criterion) { |
| 66 | + let mut group = c.benchmark_group("engine/condense"); |
| 67 | + for &scale in &[1usize, 4, 16, 64] { |
| 68 | + let lat = synthetic(scale, 4, 8); // ~37 nodes per scale unit |
| 69 | + let n = lat.len(); |
| 70 | + group.bench_with_input(BenchmarkId::from_parameter(n), &lat, |b, lat| { |
| 71 | + b.iter(|| black_box(lat.condense())) |
| 72 | + }); |
| 73 | + } |
| 74 | + group.finish(); |
43 | 75 | } |
44 | 76 |
|
45 | | -// --------------------------------------------------------------------------- |
46 | | -// B3: build_lattice — long inputs (4 KiB) |
47 | | -// --------------------------------------------------------------------------- |
48 | | -fn bench_build_lattice_long(c: &mut Criterion) { |
49 | | - let long_repo: String = "repo/".repeat(819); // ~4 KiB |
50 | | - let long_db: String = "db://".repeat(819); |
51 | | - |
52 | | - c.bench_function("build_lattice/long_4kib", |b| { |
53 | | - b.iter(|| affine::build_lattice(black_box(&long_repo), black_box(&long_db))) |
| 77 | +fn bench_zoom(c: &mut Criterion) { |
| 78 | + let lat = synthetic(16, 8, 16); // ~2.2k nodes, deep containment forest |
| 79 | + c.bench_function("engine/zoom_definitions_from_root", |b| { |
| 80 | + b.iter(|| black_box(lat.zoom(black_box(0), SemanticLevel::Definition))) |
54 | 81 | }); |
55 | 82 | } |
56 | 83 |
|
57 | | -// --------------------------------------------------------------------------- |
58 | | -// B4: query_lattice — long inputs (4 KiB) |
59 | | -// --------------------------------------------------------------------------- |
60 | | -fn bench_query_lattice_long(c: &mut Criterion) { |
61 | | - let long_node: String = "node::".repeat(682); // ~4 KiB |
62 | | - let long_db: String = "db://".repeat(819); |
63 | | - |
64 | | - c.bench_function("query_lattice/long_4kib", |b| { |
65 | | - b.iter(|| affine::query_lattice(black_box(&long_node), black_box(&long_db))) |
| 84 | +fn bench_meet(c: &mut Criterion) { |
| 85 | + let lat = synthetic(8, 8, 16); |
| 86 | + let last = lat.len() - 1; |
| 87 | + c.bench_function("engine/meet_across_forest", |b| { |
| 88 | + b.iter(|| black_box(lat.meet(black_box(1), black_box(last)))) |
66 | 89 | }); |
67 | 90 | } |
68 | 91 |
|
69 | | -// --------------------------------------------------------------------------- |
70 | | -// B5: Pipeline — build followed immediately by query |
71 | | -// --------------------------------------------------------------------------- |
72 | | -fn bench_pipeline(c: &mut Criterion) { |
73 | | - c.bench_function("pipeline/build_then_query", |b| { |
74 | | - b.iter(|| { |
75 | | - affine::build_lattice( |
76 | | - black_box("pipeline-repo"), |
77 | | - black_box("pipeline://db"), |
78 | | - ); |
79 | | - affine::query_lattice( |
80 | | - black_box("pipeline-node"), |
81 | | - black_box("pipeline://db"), |
82 | | - ); |
83 | | - }) |
| 92 | +fn bench_precedes(c: &mut Criterion) { |
| 93 | + // precedes() condenses internally on every call — bench that end-to-end cost. |
| 94 | + let lat = synthetic(4, 4, 8); |
| 95 | + let last = lat.len() - 1; |
| 96 | + c.bench_function("engine/precedes_end_to_end", |b| { |
| 97 | + b.iter(|| black_box(lat.precedes(black_box(0), black_box(last)))) |
84 | 98 | }); |
85 | 99 | } |
86 | 100 |
|
87 | | -// --------------------------------------------------------------------------- |
88 | | -// B6: Parametric — varying input lengths |
89 | | -// --------------------------------------------------------------------------- |
90 | | -fn bench_build_lattice_parametric(c: &mut Criterion) { |
91 | | - let mut group = c.benchmark_group("build_lattice/by_input_length"); |
92 | | - |
93 | | - for size in [8usize, 64, 256, 1024, 4096] { |
94 | | - let repo = "x".repeat(size); |
95 | | - let db = "d".repeat(size); |
96 | | - group.bench_with_input( |
97 | | - BenchmarkId::from_parameter(size), |
98 | | - &(repo, db), |
99 | | - |b, (r, d)| b.iter(|| affine::build_lattice(black_box(r), black_box(d))), |
100 | | - ); |
101 | | - } |
102 | | - |
103 | | - group.finish(); |
| 101 | +fn bench_ingest(c: &mut Criterion) { |
| 102 | + // Real fixture: this repo's own source tree (always present in-tree). |
| 103 | + c.bench_function("engine/ingest_src_tree", |b| { |
| 104 | + b.iter(|| black_box(ingest::from_path(black_box("src")))) |
| 105 | + }); |
104 | 106 | } |
105 | 107 |
|
106 | | -// --------------------------------------------------------------------------- |
107 | | -// Criterion entry points |
108 | | -// --------------------------------------------------------------------------- |
109 | 108 | criterion_group!( |
110 | 109 | benches, |
111 | | - bench_build_lattice_short, |
112 | | - bench_query_lattice_short, |
113 | | - bench_build_lattice_long, |
114 | | - bench_query_lattice_long, |
115 | | - bench_pipeline, |
116 | | - bench_build_lattice_parametric, |
| 110 | + bench_condense, |
| 111 | + bench_zoom, |
| 112 | + bench_meet, |
| 113 | + bench_precedes, |
| 114 | + bench_ingest, |
117 | 115 | ); |
118 | 116 | criterion_main!(benches); |
0 commit comments