Skip to content

Add PathQuery — reusable buffer-recycling path queries - #1

Merged
VladPr merged 2 commits into
mainfrom
feat/path-query-reuse
Jun 25, 2026
Merged

Add PathQuery — reusable buffer-recycling path queries#1
VladPr merged 2 commits into
mainfrom
feat/path-query-reuse

Conversation

@VladPr

@VladPr VladPr commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds cch::PathQuery — a reusable, buffer-recycling node-path query (the path-query analogue of the existing ElimTreeQuery for distances). It allocates its scratch buffers and computes the inverse-rank order once in new(), then answers each .path(metric, source, target) reusing them with a cheap touched-only reset, instead of the free node_path fn's per-call allocation + O(n) order rebuild.

let mut q = cch::PathQuery::new(&cch_view);   // buffers + order allocated once, CCH validated once
let p1 = q.path(&metric_view, a, b);          // reuses buffers
let p2 = q.path(&metric_view, c, d);

Correctness

  • Results are identical to node_path and the C++ oracle. The 200-pair LCG differential is run through a single reused PathQuery (proving the reset restores state across queries), plus interleaved/repeated/self-pair tests vs a fresh node_path.
  • The hot relaxation loops use get_unchecked on the data-dependent distance/pred accesses, made sound by a one-time up_head < node_count validation in new() (hard assert! + # Panics doc + a #[should_panic] test) — mirroring ElimTreeQuery.
  • node_path is unchanged (purely additive diff; its 200-pair gate intact). 100% line coverage held; clippy -D warnings + fmt clean.

Performance — honest assessment

pairs ×200 node_path one-shot PathQuery reused C++ (amortized)
24×24 grid (576 nodes) 2.75 ms 2.83 ms 2.88 ms
64×64 grid (4096 nodes) 50.5 ms 50.2 ms 48.9 ms

PathQuery is at parity with C++, not a clear win. Two honest takeaways from the benches:

  1. The reuse benefit is modest at these sizes (50.2 vs 50.5 ms at 4096) — per-call allocation is not the path-query bottleneck. PathQuery's value is primarily ergonomic + allocator-pressure reduction for high-throughput serving and very large graphs (where per-call allocation of million-element buffers + O(n) order rebuild compounds).
  2. At 4096 nodes both Rust paths are ~3% behind C++. The remaining gap is in the shortcut-unpack path (unpack_arc's merge-join + find_up_arc), which still has per-access bounds checks — a separate optimization, not addressed here.

Why merge it anyway

It's the correct reuse API for repeated path queries (consistent with ElimTreeQuery), it's sound + fully tested, and the per-call-allocation savings grow with graph size. It does not regress node_path.

What it does NOT do

It is not the lever that pushes path queries clearly past C++ — that's the unpack-path optimization, proposed as a focused follow-up.

VladPr added 2 commits June 25, 2026 03:08
Shows the reuse benefit is size-dependent: at 4096 nodes PathQuery (~50.2ms)
is only marginally ahead of one-shot node_path (~50.5ms) and both sit ~3%
behind amortized C++ (~48.9ms). The path-query bottleneck is the shortcut
unpack path, not per-call allocation.
@VladPr
VladPr merged commit 32545b9 into main Jun 25, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant