Stop re-validating addresses the decoder just generated - #119
Merged
Conversation
Every header's __post_init__ runs the address validators, including when the instance came from decode(). On the decode path that means a MAC rendered from six raw bytes by bytes_to_mac() is immediately matched against mac_regex to prove what the conversion already guarantees; likewise validate_ipv4_addr() against inet_ntop output. After the dispatch and MAC-rendering work landed, re.Pattern.match was the largest single remaining cost in a corpus profile. Ethernet, ARP and IPv4 — the three headers carrying addresses — now build their decoded instance directly with object.__new__ plus object.__setattr__, skipping __init__ and __post_init__ on that path. The other protocols are left alone: their __post_init__ checks are cheap integer comparisons, so bypassing them would buy little and risk more. Strictness on construction is untouched and remains the differentiator: Ethernet(dst="nonsense", ...) still raises InvalidMACAddressError, and that is now asserted next to a test that patches the compiled patterns with a spy which fails if the decode path matches at all. The __post_init__ checks the bypass skips are ones decode() has already established — IPv4's IHL is 4 bits and was rejected below 5, and the options are sliced to exactly ihl * 4 bytes after the buffer was confirmed to hold them — noted at each site and in _base.py under "Decode-path construction". A further test compares decoded instances against constructed ones, so a field the bypass forgot to set would surface immediately rather than lurk. Measured on this machine (200k calls, best of run): Ethernet.decode 2330 -> 830 ns (2.8x) IPv4.decode 4936 -> 2596 ns (1.9x) corpus walk 76,500 -> 113,200 frames/sec (1.48x) That is ~3.8 us saved per Ethernet+IPv4 frame, against the ~2.3 us the issue attributes to the regexes alone: skipping the whole constructor path, rather than only the validators, accounts for the difference. Closes#84. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QJnVMNGwTRDktC4rkABtgt
Uh oh!
There was an error while loading. Please reload this page.
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 freeto 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
On the decode path a MAC rendered from six raw bytes by
bytes_to_mac()was immediately matched againstmac_regexto prove what the conversion already guaranteed — and the same forvalidate_ipv4_addr()againstinet_ntop()output. With #82 and #83 merged,re.Pattern.matchwas the largest single remaining cost in a corpus profile.Ethernet,ARPandIPv4now build their decoded instance directly (object.__new__+object.__setattr__per field), skipping__init__/__post_init__on that path only.Strictness is unchanged — that was the thing to get right
The issue is explicit that construction-time strictness is a deliberate differentiator, so the invariant is pinned from both sides:
Ethernet(dst="nonsense", ...)still raisesInvalidMACAddressError; same forARPandIPv4. Tested.Ethernet.decode(...)runs no regex at all — tested by patching the compiled patterns with a spy whose.matchraises, so any re-validation fails the test loudly rather than silently costing time.AttributeErrorin the suite instead of lurking.The
__post_init__checks the bypass skips are onesdecode()already establishes — IPv4's IHL is 4 bits and was rejected below 5, and the options are sliced to exactlyihl * 4bytes after the buffer was confirmed to hold them. That reasoning is recorded at each site and in a new "Decode-path construction" section in_base.py.Scope was kept deliberately narrow: the other protocols'
__post_init__are cheap integer comparisons, so bypassing them would buy little and risk more.Measured
200k calls, best of run, CPython 3.12 on this machine:
Ethernet.decodeIPv4.decodeThat is ~3.8 µs saved per Ethernet+IPv4 frame, against the ~2.3 µs the issue attributes to the regexes alone — skipping the whole constructor path rather than only the validators accounts for the difference. Cumulative over #82 + #84: 58,300 → 113,200 f/s (1.94×).
Of the two options the issue weighed, this is the first (
object.__new__); a central helper taking**fieldsmeasured 855 ns against 393 ns inline, because the per-field loop dominates, so the shortcut is written out at the three sites instead.Verification
uv run ruff checkanduv run ruff format --checkare cleanuv run mypyis clean (strict)uv run pytest— 753 passedCHANGELOG.mdentry under## [Unreleased]Closes#84.
🤖 Generated with Claude Code
https://claude.ai/code/session_01QJnVMNGwTRDktC4rkABtgt
Generated by Claude Code