From 0c6fa1d5c4e819ccd18ad43ad23b670a89a0ca6d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Dec 2023 11:33:47 -1000 Subject: [PATCH 1/7] feat: Speed up unpacking TXT in ServiceInfo --- src/zeroconf/_protocol/incoming.pxd | 8 ++++---- src/zeroconf/_services/info.pxd | 3 ++- src/zeroconf/_services/info.py | 14 ++++++-------- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/zeroconf/_protocol/incoming.pxd b/src/zeroconf/_protocol/incoming.pxd index 07ae6e78e..e8fef39c7 100644 --- a/src/zeroconf/_protocol/incoming.pxd +++ b/src/zeroconf/_protocol/incoming.pxd @@ -86,19 +86,19 @@ cdef class DNSIncoming: cdef unsigned int _decode_labels_at_offset(self, unsigned int off, cython.list labels, cython.set seen_pointers) @cython.locals(offset="unsigned int") - cdef _read_header(self) + cdef void _read_header(self) - cdef _initial_parse(self) + cdef void _initial_parse(self) @cython.locals( end="unsigned int", length="unsigned int", offset="unsigned int" ) - cdef _read_others(self) + cdef void _read_others(self) @cython.locals(offset="unsigned int") - cdef _read_questions(self) + cdef void _read_questions(self) @cython.locals( length="unsigned int", diff --git a/src/zeroconf/_services/info.pxd b/src/zeroconf/_services/info.pxd index b7977466e..3506c3a91 100644 --- a/src/zeroconf/_services/info.pxd +++ b/src/zeroconf/_services/info.pxd @@ -63,7 +63,8 @@ cdef class ServiceInfo(RecordUpdateListener): @cython.locals(cache=DNSCache) cpdef bint _load_from_cache(self, object zc, cython.float now) - cdef _unpack_text_into_properties(self) + @cython.locals(length="unsigned char", index="unsigned int", key_value=bytes, key_sep_value=tuple) + cdef void _unpack_text_into_properties(self) cdef _set_properties(self, cython.dict properties) diff --git a/src/zeroconf/_services/info.py b/src/zeroconf/_services/info.py index fbf28af23..8867880d4 100644 --- a/src/zeroconf/_services/info.py +++ b/src/zeroconf/_services/info.py @@ -24,7 +24,7 @@ import random from functools import lru_cache from ipaddress import IPv4Address, IPv6Address, _BaseAddress, ip_address -from typing import TYPE_CHECKING, Dict, List, Optional, Set, Union, cast +from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Union, cast from .._dns import ( DNSAddress, @@ -395,20 +395,18 @@ def _unpack_text_into_properties(self) -> None: return index = 0 - pairs: List[bytes] = [] end = len(text) + pairs: List[Tuple[bytes, Optional[bytes]]] = [] while index < end: length = text[index] index += 1 - pairs.append(text[index : index + length]) + key_value = text[index : index + length] + key_sep_value = key_value.partition(b'=') + pairs.append((key_sep_value[0], key_sep_value[2] or None)) index += length - # Reverse the list so that the first item in the list - # is the last item in the text field. This is important - # to preserve backwards compatibility where the first - # key always wins if the key is seen multiple times. pairs.reverse() - self._properties = {key: value or None for key, _, value in (pair.partition(b'=') for pair in pairs)} + self._properties = dict(pairs) def get_name(self) -> str: """Name accessor""" From 93258ad7ca999d05f8187acd430a8ad45690ab71 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Dec 2023 11:44:50 -1000 Subject: [PATCH 2/7] feat: fixes --- src/zeroconf/_services/info.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/zeroconf/_services/info.py b/src/zeroconf/_services/info.py index 8867880d4..0e8a49d2e 100644 --- a/src/zeroconf/_services/info.py +++ b/src/zeroconf/_services/info.py @@ -24,7 +24,7 @@ import random from functools import lru_cache from ipaddress import IPv4Address, IPv6Address, _BaseAddress, ip_address -from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Union, cast +from typing import TYPE_CHECKING, Dict, List, Optional, Set, Union, cast from .._dns import ( DNSAddress, @@ -396,17 +396,18 @@ def _unpack_text_into_properties(self) -> None: index = 0 end = len(text) - pairs: List[Tuple[bytes, Optional[bytes]]] = [] + properties: Dict[Union[str, bytes], Optional[Union[str, bytes]]] = {} while index < end: length = text[index] index += 1 key_value = text[index : index + length] key_sep_value = key_value.partition(b'=') - pairs.append((key_sep_value[0], key_sep_value[2] or None)) + key = key_sep_value[0] + if key not in properties: + properties[key] = None if length == 0 else key_sep_value[2] index += length - pairs.reverse() - self._properties = dict(pairs) + self._properties = properties def get_name(self) -> str: """Name accessor""" From 93330b7b37b7314f061983891f910fde9fba3885 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Dec 2023 11:51:15 -1000 Subject: [PATCH 3/7] adjust --- src/zeroconf/_services/info.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/zeroconf/_services/info.py b/src/zeroconf/_services/info.py index 0e8a49d2e..9337a1233 100644 --- a/src/zeroconf/_services/info.py +++ b/src/zeroconf/_services/info.py @@ -404,7 +404,8 @@ def _unpack_text_into_properties(self) -> None: key_sep_value = key_value.partition(b'=') key = key_sep_value[0] if key not in properties: - properties[key] = None if length == 0 else key_sep_value[2] + value = key_sep_value[2] + properties[key] = None if len(value) == 0 else value index += length self._properties = properties From 18b004025cc33f3c171213c21eb7bfc72f87508f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Dec 2023 11:51:57 -1000 Subject: [PATCH 4/7] remove unrelated changes --- src/zeroconf/_protocol/incoming.pxd | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/zeroconf/_protocol/incoming.pxd b/src/zeroconf/_protocol/incoming.pxd index e8fef39c7..07ae6e78e 100644 --- a/src/zeroconf/_protocol/incoming.pxd +++ b/src/zeroconf/_protocol/incoming.pxd @@ -86,19 +86,19 @@ cdef class DNSIncoming: cdef unsigned int _decode_labels_at_offset(self, unsigned int off, cython.list labels, cython.set seen_pointers) @cython.locals(offset="unsigned int") - cdef void _read_header(self) + cdef _read_header(self) - cdef void _initial_parse(self) + cdef _initial_parse(self) @cython.locals( end="unsigned int", length="unsigned int", offset="unsigned int" ) - cdef void _read_others(self) + cdef _read_others(self) @cython.locals(offset="unsigned int") - cdef void _read_questions(self) + cdef _read_questions(self) @cython.locals( length="unsigned int", From 257c5ae780bb2d51937dd22b087425a2c9bcca65 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Dec 2023 11:53:41 -1000 Subject: [PATCH 5/7] remove unrelated changes --- src/zeroconf/_services/info.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/zeroconf/_services/info.py b/src/zeroconf/_services/info.py index 9337a1233..c945ca7e6 100644 --- a/src/zeroconf/_services/info.py +++ b/src/zeroconf/_services/info.py @@ -404,8 +404,7 @@ def _unpack_text_into_properties(self) -> None: key_sep_value = key_value.partition(b'=') key = key_sep_value[0] if key not in properties: - value = key_sep_value[2] - properties[key] = None if len(value) == 0 else value + properties[key] = key_sep_value[2] or None index += length self._properties = properties From 437ac10a0b78db18cbcae6f77d5ff721be7aa168 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Dec 2023 11:55:07 -1000 Subject: [PATCH 6/7] remove unrelated changes --- src/zeroconf/_services/info.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zeroconf/_services/info.py b/src/zeroconf/_services/info.py index c945ca7e6..f363b55b6 100644 --- a/src/zeroconf/_services/info.py +++ b/src/zeroconf/_services/info.py @@ -388,14 +388,14 @@ def _set_text(self, text: bytes) -> None: def _unpack_text_into_properties(self) -> None: """Unpacks the text field into properties""" text = self.text - if not text: + end = len(text) + if end == 0: # Properties should be set atomically # in case another thread is reading them self._properties = {} return index = 0 - end = len(text) properties: Dict[Union[str, bytes], Optional[Union[str, bytes]]] = {} while index < end: length = text[index] From 42af9a00a490c37607a4fb0c0b4e26264c552ce5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Dec 2023 13:08:04 -1000 Subject: [PATCH 7/7] bench --- bench/txt_properties.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 bench/txt_properties.py diff --git a/bench/txt_properties.py b/bench/txt_properties.py new file mode 100644 index 000000000..792d5312d --- /dev/null +++ b/bench/txt_properties.py @@ -0,0 +1,22 @@ +import timeit + +from zeroconf import ServiceInfo + +info = ServiceInfo( + "_test._tcp.local.", + "test._test._tcp.local.", + properties=( + b"\x19md=AlexanderHomeAssistant\x06pv=1.0\x14id=59:8A:0B:74:65:1D\x05" + b"c#=14\x04s#=1\x04ff=0\x04ci=2\x04sf=0\x0bsh=ccZLPA==" + ), +) + + +def process_properties() -> None: + info._properties = None + info.properties + + +count = 100000 +time = timeit.Timer(process_properties).timeit(count) +print(f"Processing {count} properties took {time} seconds")