Repository navigation
perf(bpa): avoid per-bundle heap allocs in RIB route lookup - #767
Merged
Merged
Conversation
The per-bundle RIB lookup (RouteTable::find_recurse, on every admitted and re-dispatched bundle) allocated two heap structures on every call: a HashSet cycle-detection trail and a Vec of candidate next-hop peers. Replace both with inline SmallVec types: - Trail: SmallVec<[&Eid; 4]> instead of HashSet<&Eid>. Via chains are short, so the trail is used purely as a small set (membership check, push on descent, pop on return) and the inline capacity keeps the common walk allocation-free. Cycle-detection semantics are preserved exactly. - PeerVec: SmallVec<[(u32, &Eid); 2]> instead of Vec. The non-ECMP case resolves to zero or one peer and no longer heap-allocates; the rare ECMP branch converts to Vec via into_vec() to keep LookupResult::ForwardEcmp and select_peer unchanged for a minimal diff. Adds smallvec (default features) as a direct bpa dependency; it was already in the lock file transitively. no_std-safe. Existing routing tests (test_recursion_loop, test_reflection*, test_union_route_member_specificity, ECMP tests) cover the behaviour and pass unchanged.
There was a problem hiding this comment.
🟢 Approval recommended
The localized changes preserve cycle detection, peer ordering, and ECMP behavior, with no blocking issues identified.
No open issues 🎉
What changed in this PR
Reduces common-path heap allocations in BPA route lookup without changing public APIs or routing behavior.
Changes:
- Uses inline
SmallVecstorage for cycle detection and candidate peers. - Preserves ECMP results by converting multi-peer collections to
Vec. - Adds
smallvecas a direct BPA dependency.
| File | Description |
|---|---|
| Cargo.lock | Records BPA’s direct smallvec dependency. |
| bpa/src/routing/table.rs | Replaces temporary collections with inline storage and updates test calls. |
| bpa/src/routing/rib.rs | Uses the new trail type for route lookups. |
| bpa/Cargo.toml | Declares the smallvec dependency. |
Review effort: Balanced
ricktaylor
approved these changes
Oct 19, 2026
ricktaylor
left a comment
Owner
There was a problem hiding this comment.
Thanks Dave, we held off because routing was in a bit of flux., but this looks 👍
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
The per-bundle RIB route lookup (
RouteTable::find_recurse, run on every admitted and re-dispatched bundle) allocated two heap structures on every call:HashSetcycle-detection trail, andVecof candidate next-hop peers.This replaces both with inline
SmallVectypes so the common lookup is allocation-free:SmallVec<[&Eid; 4]>instead ofHashSet<&Eid>. Via chains are short, so the trail is used purely as a small set (membership check, push on descent, pop on return) and the inline capacity keeps the common walk allocation-free. Cycle-detection semantics are preserved exactly.SmallVec<[(u32, &Eid); 2]>instead ofVec. The non-ECMP case resolves to zero or one peer and no longer heap-allocates. The rare ECMP branch converts toVecviainto_vec()soLookupResult::ForwardEcmpandselect_peerare unchanged, keeping the diff minimal.Adds
smallvec(default features) as a directhardy-bpadependency; it was already present inCargo.locktransitively, so the lock change is a single line.no_std-safe.No public API or routing-semantics change — this is a pure internal allocation optimisation.
Testing
CI gates run locally, all green (except the
storage-testsharness, which requires a live Postgres/S3 and is unrelated to this change):cargo fmt --check— cleancargo clippy --locked --all-targets --all-features -- -D warnings— zero warningscargo test --locked --all-features --workspace --exclude storage-tests— all passThe existing routing tests cover the behaviour and pass unchanged:
test_recursion_loop(cycle detection),test_reflection,test_reflection_no_double,test_union_route_member_specificity, and the ECMP tests (which exercise the multi-peer path and theinto_vec()conversion).