perf: O(H) lca() depth-walk + dispatch bench scoreboard

The runtime `lca()` helper used a `heapless::Vec<u16,32>` ancestry-set
with a linear `contains()` per node — O(H²) and capped at H ≤ 32. The
codegened `__DEPTH` table already had everything needed for an O(H)
depth-walk with no allocation and no hierarchy ceiling. Switch to that.

Also adds an iai-callgrind dispatch bench harness (Linux-only dev-dep)
so future optimizations can be measured deterministically by instruction
count. Seven benches cover: cold start, lateral H=2/4/8, up, self, and a
self-emit chain. Baseline numbers checked in at `hsmc/benches/baseline.txt`.

The latency analysis (`docs/003. latency-analysis.md`) is rounded out
with event-entry-point and cooperative-driving audits — three new
sections documenting the four event injection paths, the run-loop's
cooperative shape, and three correctness invariants (sticky overflow
flag, biased-select trap, cancel-safety on durings).

Bench deltas (cargo bench --bench dispatch -- --baseline=pre_lca_fix):

  h2_cold_start        -1.9%  /  -0.3%   (priming step, no lca() — noise)
  h2_warm_lateral     -22.8%  / -22.1%
  h4_cross_tree       -25.9%  / -10.6%
  h4_up                +1.0%  /  +0.4%   (up-transition, no lca() — noise)
  h4_self             -21.5%  / -11.7%
  h8_cross_tree       -46.2%  / -41.0%   ← headline: deepest LCA stress
  chain_self_emit     -15.4%  / -10.4%

Improvement scales with hierarchy depth, exactly as the O(H²) → O(H)
algorithmic change would predict.

All tests pass under --no-default-features, --features tokio, and
--features embassy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
swaitsandClaude Opus 4.7 committed 2026-04-28 16:58:53 -06:00
1 parent 4ea7463089
commit e633f5dc55
6 files changed
+997 -12

No files matched your search

+25 -12
View File
@@ -2598,20 +2598,33 @@ pub fn generate(ir: &Ir) -> TokenStream {
self.descend_defaults(target).await;
}
fn lca(a: u16, b: u16) -> Option<u16> {
let mut set: ::hsmc::__private::heapless::Vec<u16, 32> =
::hsmc::__private::heapless::Vec::new();
let mut cur = Some(a);
while let Some(x) = cur {
let _ = set.push(x);
cur = __PARENT[x as usize];
// Lowest common ancestor via depth-walk. The chart's
// `__DEPTH` table is precomputed at codegen time, so we can
// align both walkers to the same depth and then ascend in
// lockstep until they meet. O(H) time, O(1) space — no
// ancestry-set allocation, no hierarchy ceiling.
fn lca(mut a: u16, mut b: u16) -> Option<u16> {
let mut da = __DEPTH[a as usize];
let mut db = __DEPTH[b as usize];
while da > db {
match __PARENT[a as usize] {
Some(p) => { a = p; da -= 1; }
None => return None,
}
}
let mut cur = Some(b);
while let Some(x) = cur {
if set.contains(&x) { return Some(x); }
cur = __PARENT[x as usize];
while db > da {
match __PARENT[b as usize] {
Some(p) => { b = p; db -= 1; }
None => return None,
}
}
None
while a != b {
match (__PARENT[a as usize], __PARENT[b as usize]) {
(Some(pa), Some(pb)) => { a = pa; b = pb; }
_ => return None,
}
}
Some(a)
}
#[cfg(not(any(feature = "tokio", feature = "embassy")))]