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") 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..f363b55b6 100644 --- a/src/zeroconf/_services/info.py +++ b/src/zeroconf/_services/info.py @@ -388,27 +388,26 @@ 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 - pairs: List[bytes] = [] - end = len(text) + properties: Dict[Union[str, bytes], Optional[Union[str, 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'=') + key = key_sep_value[0] + if key not in properties: + properties[key] = 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 = properties def get_name(self) -> str: """Name accessor"""