Skip to content

Hoist protocol dispatch into module-level tables - #118

Merged
EONRaider merged 1 commit into
masterfrom
claude/decoder-depth-polish-8m54to
Sep 1, 2026
Merged

Hoist protocol dispatch into module-level tables#118
EONRaider merged 1 commit into
masterfrom
claude/decoder-depth-polish-8m54to

Conversation

@EONRaider

Copy link
Copy Markdown
Owner

Summary

_ethertype_class() and _ip_protocol_class() re-ran their deferred imports and rebuilt a dict literal 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_CLASSES and _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.
  • The imports stay inside the build functions, so the layer modules remain acyclic exactly as before (ARCHITECTURE.md's deferred-import rule is untouched); only the rebuilding goes away.
  • New test asserting the two tables differ only by the IPv6-only numbers, alongside the existing public-API gating test (test_ipv4_never_dispatches_extension_headers).

Measured

200k calls, best of run, CPython 3.12 on this machine:

beforeafter
_ip_protocol_class1439 ns104 ns13.9×
_ethertype_class712 ns86 ns8.3×
corpus walk (97 frames)58,300 f/s76,500 f/s1.31×

A 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 check and uv run ruff format --check are clean
  • uv run mypy is clean (strict)
  • uv run pytest — 748 passed
  • CHANGELOG.md entry 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

_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
@EONRaider
EONRaider merged commit 79253c8 into masterSep 1, 2026
6 checks passed
Sign up for freeto 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.

Hoist protocol dispatch into module-level tables

2 participants

@EONRaider@claude