Skip to content

perf(claims): intern common claim/header keys in json_to_bound (#123) - #129

Merged
ZhuchkaTriplesix merged 2 commits into
devfrom
issue/123-intern-claim-keys
Sep 28, 2026
Merged

ZhuchkaTriplesix merged 2 commits into
devfrom
issue/123-intern-claim-keys

Conversation

@ZhuchkaTriplesix

Copy link
Copy Markdown
Member

Problem

json_to_bound (used by every decode/decode_complete call) allocated a fresh PyString for every dict key on every call, including the same standard claim/header names every time.

Fix

The ten standard names — exp, iat, nbf, sub, aud, iss, jti, alg, typ, kid — now come from pyo3::intern!, which caches each key in a call-site-local static instead of allocating it fresh. Interning also registers the string in CPython's own intern table, so a later payload.get("exp") on the Python side (a str literal, which CPython auto-interns) can hit the identity-comparison fast path during dict lookup instead of a full string comparison. Any other key still falls back to an ordinary, uninterned PyString, unchanged from before.

No behavioral change.

Results

Native decode, 8-claim payload (sub, iat, exp, nbf, iss, jti, plus two custom keys), release build, averaged over repeated runs:

before after
native decode ~2.05 µs ~1.92 µs (~6% less)

Modest, as expected — allocation is a small fraction of total decode cost next to base64/JSON/signature verification — but real and reproducible.

Tests

Added test_standard_claim_key_is_interned, test_standard_header_key_is_interned and test_custom_claim_key_is_not_interned (tests/test_encode_decode.py): each standard name must decode back as the exact same interned object (is, not just ==), while a key outside the list must still decode correctly as an ordinary, uninterned string.

(Considered a #[cfg(test)] Rust unit test instead/also, but this crate's existing Rust tests are pure-logic and never touch the Python API — adding one that does would need the auto-initialize pyo3 feature as a new dev-dependency, which changes cargo test's requirements project-wide for a change this contained. The Python-level identity test gives the same guarantee through the existing test infrastructure.)

Checklist

  • Common keys are interned
  • Micro-benchmark (8-claim payload decode) shows improvement; numbers above
  • cargo build/clippy --all-targets -D warnings/fmt --check/cargo test clean with both aws_lc_rs (default) and --no-default-features --features rust_crypto
  • Full pytest (270 passed, 1 skipped) and mypy clean
  • No behavioral change

Closes #123.

json_to_bound allocated a fresh PyString for every dict key on every
decode, including the same standard claim/header names every time:
exp, iat, nbf, sub, aud, iss, jti, alg, typ, kid.

Those ten keys now come from pyo3::intern!, which caches each key in
a call-site-local static rather than allocating it fresh. Interning
also registers the string in CPython's own intern table, so a
subsequent payload.get("exp") on the Python side (a str literal,
which CPython also interns) can hit the identity-comparison fast path
during dict lookup instead of a full string comparison. Any other key
still falls back to an ordinary, uninterned PyString, unchanged from
before.

Measured (native decode, 8-claim payload, release build):
~2.05us -> ~1.92us (~6% less).

No behavioural change.

Closes #123
Regression coverage for the previous commit: each standard claim name
and each standard header field must decode back as the exact same
interned str object pyo3 caches for it (identity, not just equality),
while a key outside that list must still decode correctly as an
ordinary, uninterned str.
@ZhuchkaTriplesix
ZhuchkaTriplesix merged commit f594932 into dev Sep 28, 2026
7 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