Reusable + parallel CCH customization (bit-identical) - #2
Merged
Conversation
Cch::customize now delegates to a new Customizer (via Cch::customizer, which computes the elimination-tree Levels partition once). Customizer adds customize_into for callers who want to reuse a Metric's forward/ backward allocations across repeated customizations of the same structure. Cch::customize keeps its exact signature, panic behavior, and byte-for-byte output.
Replace the serial phase-2 relaxation with a level-synchronized parallel version: levels run sequentially, nodes within a level run in parallel via rayon with disjoint writes through a raw-pointer DisjointArcs wrapper. Cch::customizer now validates structural invariants once so the raw-pointer accesses are sound for any Cch, including one loaded from arbitrary bytes. Retains an independent serial relaxation as a #[cfg(test)] reference and adds property tests checking the parallel path against it and against itself for determinism, plus should_panic tests covering each validation branch.
…ormed Cch; fix stale comment The parallel write-disjointness in relax_node/DisjointArcs relies on the CCH being well-formed (chordal); customizer()'s validation only checks bounds. Narrow the safety/doc language to say so explicitly instead of over-claiming soundness "however self was built" or that the asserts guard against "hand-corrupted or foreign-loaded" input — a bounds-valid but non-chordal Cch is out of the safety contract. Also fix a stale SAFETY comment that referenced get_unchecked; the code only uses raw pointer .add(). No logic or runtime behavior changes.
Add a customize_reuse bench comparing fresh Cch::customize per call against a reused Customizer::customize_into on the 24x24 grid fixture (cargo bench --bench cch -- customize). Observed: fresh_each_call ~1.238 ms median vs reused_customizer ~1.200 ms median, ~3% faster with no allocation and no level-partition recompute; the gap is capped on this small grid by parallel overhead and grows with structure size and call frequency. Update README (deps framing now that rayon is a dependency, a new Highlights bullet, quick-start reuse comment, performance note, and moved parallel customization from Planned to shipped) and the crate rustdoc in src/lib.rs to document Cch::customizer / Customizer::customize_into for repeated, allocation-free, parallel customization. Also fix a pre-existing broken intra-doc link in query.rs ([`Cch::build`] unresolved outside the crate root) that made `cargo doc --no-deps` emit a warning.
… panics The phase-2 level loop allocated a fresh node_count-sized Vec per rayon worker on every level via for_each_init, up to num_levels x num_threads allocations per customize_into call. Replace it with a thread_local scratch cache allocated once per worker and reused across levels and across customize_into calls, grown on demand. Also document that Cch::customize can panic on a structurally malformed Cch (delegated to customizer()) and note that repeated callers should hold a Customizer to avoid re-validating and re-partitioning each call.
Bump 0.1.1 -> 0.2.0 (minor: new Customizer API + rayon dep). Add 'customize in parallel' to the crates.io description, update the README install pin to 0.2, and add a Keep-a-Changelog CHANGELOG backfilling 0.1.0/0.1.1.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes per-metric CCH customization reusable (no per-call allocation) and parallel (rayon, level-synchronized), while keeping output bit-identical to the previous serial
customizeand to the C++ RoutingKit oracle.Customization is the operation a routing service runs most often (build once, re-customize whenever weights change). This branch addresses its two costs: fresh allocation of the
forward/backward/scratch arrays every call, and single-threaded relaxation that leaves all other cores idle.What changed
Customizer<'a>(cch.customizer()) — owns the metric-independent elimination-tree level partition (derived once) and reuses the caller'sMetricbuffers viacustomize_into(&self, weights, &mut Metric).Cch::customize(weights) -> Metricis unchanged (same signature, same panics, byte-for-byte identical output) — now a thin wrapper overCustomizer. No downstream break.Send + SyncDisjointArcswrapper, guarded by one-time bounds validation incustomizer(). Per-thread relaxation scratch is a reusedthread_local!(allocated once, reused across levels and calls).Correctness
min(associative + commutative) over a fixed dependency DAG, so any level-respecting schedule yields the identical result to the serial loop.tests/equivalence.rs), an independent#[cfg(test)]serial reference (parallel_relax_equals_serial_reference), a determinism test, and reuse/no-bleed tests.unsafe's data-race-freedom is contracted to a well-formed (chordal) CCH as produced bybuildor a faithfulload_structround-trip; the bounds validation additionally prevents OOB for any bounds-valid structure. miri (Tree Borrows) passes on the customize tests; the Stacked-Borrows warning was traced to a known rayon/crossbeam-epoch artifact in the safe phase-1 path, not the newunsafe.cargo llvm-cov --fail-under-lines 100);clippy --all-targets -D warningsandrustfmtclean.Notes
benches/cch.rs, 24×24 grid): reusedCustomizeris never slower than freshcustomize; the reuse win grows with structure size (the parallel relaxation dominates the small fixture). Runcargo benchto reproduce.Customizerreuse pattern documented.