Target: 1.4.0 (Tier 1 — earn the benchmark)
layer7/dns.py calls bytes(self) — a full re-serialization of the
whole DNS message — from six separate sites (lines 190, 213, 259, 287,
314, 346). _labels() is called once per record and again per name
inside _decode_rdata(), so record parsing is O(records × message
size) in allocations.
Worse, answers, authorities and additionals (dns.py:390-403)
each call _resource_records(), which re-parses all three sections
from scratch. Reading all three parses the message three times.
Nothing in the library caches anything — there is no functools
import, no cached_property, no lru_cache anywhere in src/.
Measured
On a 96-byte response with 3 records:
| Access | Cost |
|---|
.answers alone | 19.0 µs |
.answers + .authorities + .additionals | 55.7 µs (2.9×) |
bytes(self) calls for one .answers | 14 |
For scale, decoding an entire frame takes ~26 µs. One DNS accessor
costs most of a frame.
What to do
Two independent wins, either order:
- Parse the three sections once and slice the result, rather than
re-parsing per accessor. - Stop round-tripping through
bytes(self) — the parser should work
from the already-held sections bytes plus the header, not
re-serialize the object it is a method on.
Caching on a frozen dataclass needs object.__setattr__ or a
module-level memo; functools.cached_property does not work with
slots=True. Whatever is chosen must not break the
bytes(decode(x)) == x guarantee or make instances unhashable.
Acceptance criteria
Target: 1.4.0 (Tier 1 — earn the benchmark)
layer7/dns.pycallsbytes(self)— a full re-serialization of thewhole DNS message — from six separate sites (lines 190, 213, 259, 287,
314, 346).
_labels()is called once per record and again per nameinside
_decode_rdata(), so record parsing is O(records × messagesize) in allocations.
Worse,
answers,authoritiesandadditionals(dns.py:390-403)each call
_resource_records(), which re-parses all three sectionsfrom scratch. Reading all three parses the message three times.
Nothing in the library caches anything — there is no
functoolsimport, no
cached_property, nolru_cacheanywhere insrc/.Measured
On a 96-byte response with 3 records:
.answersalone.answers+.authorities+.additionalsbytes(self)calls for one.answersFor scale, decoding an entire frame takes ~26 µs. One DNS accessor
costs most of a frame.
What to do
Two independent wins, either order:
re-parsing per accessor.
bytes(self)— the parser should workfrom the already-held
sectionsbytes plus the header, notre-serialize the object it is a method on.
Caching on a frozen dataclass needs
object.__setattr__or amodule-level memo;
functools.cached_propertydoes not work withslots=True. Whatever is chosen must not break thebytes(decode(x)) == xguarantee or make instances unhashable.Acceptance criteria
bytes(self)is not called from any accessor.