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:
6 files changed
+997
-12
No files matched your search
+25
-12
@@ -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")))]
|
||||
|
||||
Reference in new issue
Block a user