perf(context): cut ruv:// parse cost by ~18%, stop allocating in fingerprint - #51
Merged
Conversation
…ingerprint A gateway placing the ruv:// gate in front of its own traffic parses a name on every request, so parse is the hot path. Three redundancies, each measured rather than assumed. reject_forbidden_octets ran once over the whole input in from_str and then AGAIN inside Authority::new, TenantId::new, SubjectId::new, and every PathSegment::new -- seven calls for a three-segment name, each making three separate passes (is_ascii, contains(b'%'), contains(b'#')). Since from_str validates the entire input before it splits on '/', every component it derives is already known clean. Each constructor now splits into a public `new` that keeps the scan for callers arriving with unchecked text, and a private `new_prevalidated` that from_str uses. Worth -14.6% on its own. The octet scan itself now makes ONE pass collecting three verdicts, ranked after the scan rather than during it, so which error a caller sees still does not depend on which forbidden byte appears first. Worth a further -4.4%. ContextScope::fingerprint called .to_string() on the authority, tenant, collection and every path segment purely to read their bytes -- an allocation per component on the capability-minting path. Display for each of those types is exactly `f.write_str(self.as_str())`, so as_str hashes byte-identical input. Because a scope fingerprint becomes a capability badge, the values are pinned in a new test captured BEFORE the rewrite; they are unchanged. Measured with criterion --save-baseline against unmodified main, same bench file both sides: ruv_uri_parse_alias 293.4ns -> 243.2ns -17.6% ruv_uri_parse_pinned 285.9ns -> 236.1ns -18.0% ruv_uri_parse_bare 177.9ns -> 134.9ns -24.5% scope_from_uri 36.5ns -> 35.0ns -3.9% Also benchmarks scope containment, which had no coverage: it is 4.7-7.7ns and allocation-free, so the shadow-mode question costs ~7ns once the name is parsed -- parse dominates per-request cost by roughly 30x. contains_scope itself is untouched; its +2.6% on one input is 0.18ns of codegen noise. Tried and reverted: Box<str> component storage shrinks RuvUri from 144 to 120 bytes but regressed parse ~3%, so it lost on the metric being optimised. 1,280 workspace tests pass. aarch64-unknown-none and the wasm32 binding both still build clean. Co-Authored-By: claude-flow <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_016QSCkKnxDjqU49NVVpWMK5
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.
A gateway placing the
ruv://gate in front of its own traffic parses a name on every request, so parse is the hot path. Three redundancies, each measured rather than assumed.Measurements
criterion --save-baselineagainst unmodifiedmain, same bench file both sides:ruv_uri_parse_aliasruv_uri_parse_pinnedruv_uri_parse_barescope_from_uriWhat changed
1. Redundant octet scans (−14.6% alone).
reject_forbidden_octetsran once over the whole input infrom_str, then again insideAuthority::new,TenantId::new,SubjectId::newand everyPathSegment::new— seven calls for a three-segment name, each making three separate passes. Sincefrom_strvalidates the entire input before splitting on/, every component it derives is already known clean. Each constructor splits into a publicnewthat keeps the scan for callers arriving with unchecked text, and a privatenew_prevalidatedused byfrom_str.2. Three passes → one (−4.4% more). The scan now collects three verdicts in a single pass, ranked after the scan rather than during it, so which error a caller sees still doesn't depend on which forbidden byte happens to appear first. Error precedence is unchanged.
3.
fingerprintstops allocating. It called.to_string()on the authority, tenant, collection and every path segment purely to read their bytes.Displayfor each is exactlyf.write_str(self.as_str()), soas_strhashes byte-identical input.A scope fingerprint becomes a capability badge, so those bytes are an identity, not an implementation detail. I captured the values before the rewrite and pinned them in a new test — they are unchanged, and the test makes that suite-checkable rather than reviewer-checkable.
New coverage
Scope containment had no benchmark at all, despite being the whole shadow-mode question. It measures 4.7–7.7 ns and is allocation-free — so once a name is parsed, deciding whether it falls inside a grant is ~7 ns, and parse dominates per-request cost by roughly 30×. If shadow-mode throughput ever matters, parse is where to look, not containment.
Added a bare-collection parse case too, which isolates the per-path-segment allocation cost (~35 ns/segment).
Tried and reverted
Box<str>component storage shrinksRuvUrifrom 144 → 120 bytes but regressed parse ~3%, so it lost on the metric being optimised and is not in this branch.Honest note
scope_contains_miss_last_segmentshows +2.6%.contains_scopeis untouched by this branch; that is 0.18 ns of codegen noise on a 7 ns measurement, not a real regression.Verification
1,280 workspace tests pass.
aarch64-unknown-noneand thewasm32-unknown-unknownbinding both still build clean, andrvm-context-wasmclippy is clean under-D warnings.🤖 Generated with claude-flow
https://claude.ai/code/session_016QSCkKnxDjqU49NVVpWMK5