Skip to content

Commit b3715ba

Browse files
committed
feat: optimize equality checks for DNS records
By moving some of the equality checking to `cdef`s we can avoid the generic python accessors and access the properties directly on the object. This can only be done once it passes the `isinstance` check for the same type.
1 parent 255a884 commit b3715ba

2 files changed

Lines changed: 99 additions & 57 deletions

File tree

src/zeroconf/_dns.pxd

Lines changed: 54 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11

2+
import cython
23

34

45
cdef object _LEN_BYTE
@@ -12,67 +13,91 @@ cdef object _EXPIRE_FULL_TIME_MS
1213
cdef object _EXPIRE_STALE_TIME_MS
1314
cdef object _RECENT_TIME_MS
1415

16+
cdef object _CLASS_UNIQUE
17+
cdef object _CLASS_MASK
1518

1619
cdef class DNSEntry:
1720

18-
cdef public key
19-
cdef public name
20-
cdef public type
21-
cdef public class_
22-
cdef public unique
21+
cdef public object key
22+
cdef public object name
23+
cdef public object type
24+
cdef public object class_
25+
cdef public object unique
2326

2427
cdef class DNSQuestion(DNSEntry):
2528

26-
cdef public _hash
29+
cdef public cython.int _hash
2730

2831
cdef class DNSRecord(DNSEntry):
2932

30-
cdef public ttl
31-
cdef public created
33+
cdef public object ttl
34+
cdef public object created
35+
36+
cdef _suppressed_by_answer(self, DNSRecord answer)
37+
3238

3339
cdef class DNSAddress(DNSRecord):
3440

35-
cdef public _hash
36-
cdef public address
37-
cdef public scope_id
41+
cdef public cython.int _hash
42+
cdef public object address
43+
cdef public object scope_id
44+
45+
cdef _eq(self, DNSAddress other)
3846

3947

4048
cdef class DNSHinfo(DNSRecord):
4149

42-
cdef public _hash
43-
cdef public cpu
44-
cdef public os
50+
cdef public cython.int _hash
51+
cdef public object cpu
52+
cdef public object os
53+
54+
cdef _eq(self, DNSHinfo other)
4555

4656

4757
cdef class DNSPointer(DNSRecord):
4858

49-
cdef public _hash
50-
cdef public alias
59+
cdef public cython.int _hash
60+
cdef public object alias
61+
62+
cdef _eq(self, DNSPointer other)
63+
5164

5265
cdef class DNSText(DNSRecord):
5366

54-
cdef public _hash
55-
cdef public text
67+
cdef public cython.int _hash
68+
cdef public object text
69+
70+
cdef _eq(self, DNSText other)
71+
5672

5773
cdef class DNSService(DNSRecord):
5874

59-
cdef public _hash
60-
cdef public priority
61-
cdef public weight
62-
cdef public port
63-
cdef public server
64-
cdef public server_key
75+
cdef public cython.int _hash
76+
cdef public object priority
77+
cdef public object weight
78+
cdef public object port
79+
cdef public object server
80+
cdef public object server_key
81+
82+
cdef _eq(self, DNSService other)
83+
6584

6685
cdef class DNSNsec(DNSRecord):
6786

68-
cdef public _hash
69-
cdef public next_name
70-
cdef public rdtypes
87+
cdef public cython.int _hash
88+
cdef public object next_name
89+
cdef public cython.list rdtypes
90+
91+
cdef _eq(self, DNSNsec other)
7192

7293

7394
cdef class DNSRRSet:
7495

7596
cdef _records
76-
cdef _lookup
97+
cdef cython.dict _lookup
98+
99+
@cython.locals(other=DNSRecord)
100+
cpdef suppresses(self, DNSRecord record)
101+
77102

78-
cdef _dns_entry_matches(DNSEntry entry, object key, object type_, object class_)
103+
cdef _dns_entry_matches(DNSEntry entry, str key, cython.int type_, cython.int class_)

src/zeroconf/_dns.py

Lines changed: 45 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,9 @@ def __eq__(self, other: Any) -> bool: # pylint: disable=no-self-use
169169
def suppressed_by(self, msg: 'DNSIncoming') -> bool:
170170
"""Returns true if any answer in a message can suffice for the
171171
information held in this record."""
172-
return any(self.suppressed_by_answer(record) for record in msg.answers)
172+
return any(self._suppressed_by_answer(record) for record in msg.answers)
173173

174-
def suppressed_by_answer(self, other: 'DNSRecord') -> bool:
174+
def _suppressed_by_answer(self, other) -> bool: # type: ignore[no-untyped-def]
175175
"""Returns true if another record has same name, type and class,
176176
and if its TTL is at least half of this record's."""
177177
return self == other and other.ttl > (self.ttl / 2)
@@ -246,9 +246,11 @@ def write(self, out: 'DNSOutgoing') -> None:
246246

247247
def __eq__(self, other: Any) -> bool:
248248
"""Tests equality on address"""
249+
return isinstance(other, DNSAddress) and self._eq(other)
250+
251+
def _eq(self, other) -> bool: # type: ignore[no-untyped-def]
249252
return (
250-
isinstance(other, DNSAddress)
251-
and self.address == other.address
253+
self.address == other.address
252254
and self.scope_id == other.scope_id
253255
and _dns_entry_matches(other, self.key, self.type, self.class_)
254256
)
@@ -289,10 +291,13 @@ def write(self, out: 'DNSOutgoing') -> None:
289291
out.write_character_string(self.os.encode('utf-8'))
290292

291293
def __eq__(self, other: Any) -> bool:
292-
"""Tests equality on cpu and os"""
294+
"""Tests equality on cpu and os."""
295+
return isinstance(other, DNSHinfo) and self._eq(other)
296+
297+
def _eq(self, other) -> bool: # type: ignore[no-untyped-def]
298+
"""Tests equality on cpu and os."""
293299
return (
294-
isinstance(other, DNSHinfo)
295-
and self.cpu == other.cpu
300+
self.cpu == other.cpu
296301
and self.os == other.os
297302
and _dns_entry_matches(other, self.key, self.type, self.class_)
298303
)
@@ -334,12 +339,12 @@ def write(self, out: 'DNSOutgoing') -> None:
334339
out.write_name(self.alias)
335340

336341
def __eq__(self, other: Any) -> bool:
337-
"""Tests equality on alias"""
338-
return (
339-
isinstance(other, DNSPointer)
340-
and self.alias == other.alias
341-
and _dns_entry_matches(other, self.key, self.type, self.class_)
342-
)
342+
"""Tests equality on alias."""
343+
return isinstance(other, DNSPointer) and self._eq(other)
344+
345+
def _eq(self, other) -> bool: # type: ignore[no-untyped-def]
346+
"""Tests equality on alias."""
347+
return self.alias == other.alias and _dns_entry_matches(other, self.key, self.type, self.class_)
343348

344349
def __hash__(self) -> int:
345350
"""Hash to compare like DNSPointer."""
@@ -373,12 +378,12 @@ def __hash__(self) -> int:
373378
return self._hash
374379

375380
def __eq__(self, other: Any) -> bool:
376-
"""Tests equality on text"""
377-
return (
378-
isinstance(other, DNSText)
379-
and self.text == other.text
380-
and _dns_entry_matches(other, self.key, self.type, self.class_)
381-
)
381+
"""Tests equality on text."""
382+
return isinstance(other, DNSText) and self._eq(other)
383+
384+
def _eq(self, other) -> bool: # type: ignore[no-untyped-def]
385+
"""Tests equality on text."""
386+
return self.text == other.text and _dns_entry_matches(other, self.key, self.type, self.class_)
382387

383388
def __repr__(self) -> str:
384389
"""String representation"""
@@ -411,7 +416,7 @@ def __init__(
411416
self.port = port
412417
self.server = server
413418
self.server_key = server.lower()
414-
self._hash = hash((self.key, type_, self.class_, priority, weight, port, server))
419+
self._hash = hash((self.key, type_, self.class_, priority, weight, port, self.server_key))
415420

416421
def write(self, out: 'DNSOutgoing') -> None:
417422
"""Used in constructing an outgoing packet"""
@@ -422,12 +427,15 @@ def write(self, out: 'DNSOutgoing') -> None:
422427

423428
def __eq__(self, other: Any) -> bool:
424429
"""Tests equality on priority, weight, port and server"""
430+
return isinstance(other, DNSService) and self._eq(other)
431+
432+
def _eq(self, other) -> bool: # type: ignore[no-untyped-def]
433+
"""Tests equality on priority, weight, port and server."""
425434
return (
426-
isinstance(other, DNSService)
427-
and self.priority == other.priority
435+
self.priority == other.priority
428436
and self.weight == other.weight
429437
and self.port == other.port
430-
and self.server == other.server
438+
and self.server_key == other.server_key
431439
and _dns_entry_matches(other, self.key, self.type, self.class_)
432440
)
433441

@@ -478,10 +486,13 @@ def write(self, out: 'DNSOutgoing') -> None:
478486
out.write_string(out_bytes)
479487

480488
def __eq__(self, other: Any) -> bool:
481-
"""Tests equality on cpu and os"""
489+
"""Tests equality on next_name and rdtypes."""
490+
return isinstance(other, DNSNsec) and self._eq(other)
491+
492+
def _eq(self, other) -> bool: # type: ignore[no-untyped-def]
493+
"""Tests equality on next_name and rdtypes."""
482494
return (
483-
isinstance(other, DNSNsec)
484-
and self.next_name == other.next_name
495+
self.next_name == other.next_name
485496
and self.rdtypes == other.rdtypes
486497
and _dns_entry_matches(other, self.key, self.type, self.class_)
487498
)
@@ -497,6 +508,9 @@ def __repr__(self) -> str:
497508
)
498509

499510

511+
_DNSRecord = DNSRecord
512+
513+
500514
class DNSRRSet:
501515
"""A set of dns records independent of the ttl."""
502516

@@ -514,10 +528,13 @@ def lookup(self) -> Dict[DNSRecord, DNSRecord]:
514528
self._lookup = {record: record for record in self._records}
515529
return self._lookup
516530

517-
def suppresses(self, record: DNSRecord) -> bool:
531+
def suppresses(self, record: _DNSRecord) -> bool:
518532
"""Returns true if any answer in the rrset can suffice for the
519533
information held in this record."""
520-
other = self.lookup.get(record)
534+
if self._lookup is None:
535+
other = self.lookup.get(record)
536+
else:
537+
other = self._lookup.get(record)
521538
return bool(other and other.ttl > (record.ttl / 2))
522539

523540
def __contains__(self, record: DNSRecord) -> bool:

0 commit comments

Comments
 (0)