Hoist protocol dispatch into module-level tables - #118
Merged
Conversation
_ethertype_class() and _ip_protocol_class() re-ran their deferred imports and rebuilt a dict literal on every call. They run once per layer per frame, so this was the hottest path in the library — a corpus profile put _ip_protocol_class at the top by tottime. Build each table once, on first use, and reduce the call to a lookup. The imports stay inside the build function, so the layer modules remain acyclic exactly as before; only the rebuilding goes away. The IPv6-only gating is preserved by construction rather than by a per-call membership test: the IPv4 table is the IPv6 table minus the extension-header numbers, so an IPv4 packet with protocol=0 still cannot decode a Hop-by-Hop layer. That invariant now has a table-level test alongside the existing public-API one. Measured on this machine (200k calls, best of run): _ip_protocol_class 1439 -> 104 ns/call (13.9x) _ethertype_class 712 -> 86 ns/call (8.3x) corpus walk 58,300 -> 76,500 frames/sec (1.31x) The issue quotes 91x/42x for the two functions; those figures compare against a bare dict lookup, while the numbers above are through the function call, whose overhead now dominates at ~90-100 ns. The end-to-end corpus figure is the one that matters for #86. Closes#82. 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
_ethertype_class()and_ip_protocol_class()re-ran their deferred imports and rebuilt adictliteral on every call — once per layer per frame, the hottest path in the library. Both now build their table once, on first use, and reduce the call to a lookup. Internal only; no API change.What's included
layer2/ethernet.py:_ETHERTYPE_CLASSES, populated by_build_ethertype_classes()on first use. The VLAN tag types fold into the same table (dict.fromkeys) instead of a separate membership test.layer3/ip.py:_IPV6_PROTOCOL_CLASSESand_IPV4_PROTOCOL_CLASSES. The gating is baked into the tables — the IPv4 table is the IPv6 one minus_IPV6_ONLY_NUMBERS— so it costs nothing per call and cannot drift.test_ipv4_never_dispatches_extension_headers).Measured
200k calls, best of run, CPython 3.12 on this machine:
_ip_protocol_class_ethertype_classA note on the issue's 91×/42×: those compare against a bare dict lookup. The figures above are measured through the function call, whose overhead (~90–100 ns) now dominates — the table lookup itself is a few ns. The end-to-end corpus number is the one #86 will gate on.
Verification
uv run ruff checkanduv run ruff format --checkare cleanuv run mypyis clean (strict)uv run pytest— 748 passedCHANGELOG.mdentry under## [Unreleased]Notes
#87 (Tier 2) generalises this into a public registry. This lands the table as a private implementation detail so the registry design isn't forced now; the shapes are deliberately compatible.
Closes#82.
🤖 Generated with Claude Code
https://claude.ai/code/session_01QJnVMNGwTRDktC4rkABtgt
Generated by Claude Code