feat: speed up unpacking TXT record data in ServiceInfo (#1318) · python-zeroconf/python-zeroconf@a200842

3 files changed

lines changed

Original file line numberDiff line numberDiff line change

@@ -0,0 +1,22 @@

1+

import timeit

2+
3+

from zeroconf import ServiceInfo

4+
5+

info = ServiceInfo(

6+

"_test._tcp.local.",

7+

"test._test._tcp.local.",

8+

properties=(

9+

b"\x19md=AlexanderHomeAssistant\x06pv=1.0\x14id=59:8A:0B:74:65:1D\x05"

10+

b"c#=14\x04s#=1\x04ff=0\x04ci=2\x04sf=0\x0bsh=ccZLPA=="

11+

),

12+

)

13+
14+
15+

def process_properties() -> None:

16+

info._properties = None

17+

info.properties

18+
19+
20+

count = 100000

21+

time = timeit.Timer(process_properties).timeit(count)

22+

print(f"Processing {count} properties took {time} seconds")

Original file line numberDiff line numberDiff line change

@@ -63,7 +63,8 @@ cdef class ServiceInfo(RecordUpdateListener):

6363

@cython.locals(cache=DNSCache)

6464

cpdef bint _load_from_cache(self, object zc, cython.float now)

6565
66-

cdef _unpack_text_into_properties(self)

66+

@cython.locals(length="unsigned char", index="unsigned int", key_value=bytes, key_sep_value=tuple)

67+

cdef void _unpack_text_into_properties(self)

6768
6869

cdef _set_properties(self, cython.dict properties)

6970
Original file line numberDiff line numberDiff line change

@@ -388,27 +388,26 @@ def _set_text(self, text: bytes) -> None:

388388

def _unpack_text_into_properties(self) -> None:

389389

"""Unpacks the text field into properties"""

390390

text = self.text

391-

if not text:

391+

end = len(text)

392+

if end == 0:

392393

# Properties should be set atomically

393394

# in case another thread is reading them

394395

self._properties = {}

395396

return

396397
397398

index = 0

398-

pairs: List[bytes] = []

399-

end = len(text)

399+

properties: Dict[Union[str, bytes], Optional[Union[str, bytes]]] = {}

400400

while index < end:

401401

length = text[index]

402402

index += 1

403-

pairs.append(text[index : index + length])

403+

key_value = text[index : index + length]

404+

key_sep_value = key_value.partition(b'=')

405+

key = key_sep_value[0]

406+

if key not in properties:

407+

properties[key] = key_sep_value[2] or None

404408

index += length

405409
406-

# Reverse the list so that the first item in the list

407-

# is the last item in the text field. This is important

408-

# to preserve backwards compatibility where the first

409-

# key always wins if the key is seen multiple times.

410-

pairs.reverse()

411-

self._properties = {key: value or None for key, _, value in (pair.partition(b'=') for pair in pairs)}

410+

self._properties = properties

412411
413412

def get_name(self) -> str:

414413

"""Name accessor"""