From 5825758991a5d5f476b082c0277e5ecb0767c7e5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 7 Jan 2025 11:56:03 -1000 Subject: [PATCH] feat: speed up unmarshalling headers (#347) --- src/dbus_fast/_private/unmarshaller.pxd | 19 ++++++++++++-- src/dbus_fast/_private/unmarshaller.py | 33 +++++++++++++++++++------ src/dbus_fast/message.py | 4 +-- 3 files changed, 44 insertions(+), 12 deletions(-) diff --git a/src/dbus_fast/_private/unmarshaller.pxd b/src/dbus_fast/_private/unmarshaller.pxd index 94ae1c5..ff5808a 100644 --- a/src/dbus_fast/_private/unmarshaller.pxd +++ b/src/dbus_fast/_private/unmarshaller.pxd @@ -22,7 +22,18 @@ cdef unsigned int HEADER_SIGNATURE_SIZE cdef unsigned int LITTLE_ENDIAN cdef unsigned int BIG_ENDIAN cdef unsigned int PROTOCOL_VERSION + + +cdef unsigned int HEADER_PATH_IDX +cdef unsigned int HEADER_INTERFACE_IDX +cdef unsigned int HEADER_MEMBER_IDX +cdef unsigned int HEADER_ERROR_NAME_IDX +cdef unsigned int HEADER_REPLY_SERIAL_IDX +cdef unsigned int HEADER_DESTINATION_IDX +cdef unsigned int HEADER_SENDER_IDX +cdef unsigned int HEADER_SIGNATURE_IDX cdef unsigned int HEADER_UNIX_FDS_IDX + cdef cython.list HEADER_IDX_TO_ARG_NAME cdef str UINT32_CAST @@ -95,6 +106,8 @@ cdef unsigned int TOKEN_LEFT_PAREN_AS_INT cdef object MARSHALL_STREAM_END_ERROR cdef object DEFAULT_BUFFER_SIZE +cdef list _EMPTY_HEADERS + cdef cython.uint EAGAIN cdef cython.uint EWOULDBLOCK @@ -222,9 +235,10 @@ cdef class Unmarshaller: @cython.locals( body=cython.list, - header_fields=cython.dict, + header_fields=cython.list, token_as_int=cython.uint, signature=cython.str, + message=Message ) cdef _read_body(self) @@ -237,5 +251,6 @@ cdef class Unmarshaller: o=cython.ulong, token_as_int=cython.uint, signature_len=cython.uint, + headers=cython.list ) - cdef cython.dict _header_fields(self, unsigned int header_length) + cdef cython.list _header_fields(self, unsigned int header_length) diff --git a/src/dbus_fast/_private/unmarshaller.py b/src/dbus_fast/_private/unmarshaller.py index a2327df..d5181f5 100644 --- a/src/dbus_fast/_private/unmarshaller.py +++ b/src/dbus_fast/_private/unmarshaller.py @@ -134,8 +134,18 @@ HEADER_IDX_TO_ARG_NAME = [ "signature", "unix_fds", ] +HEADER_PATH_IDX = HEADER_IDX_TO_ARG_NAME.index("path") +HEADER_INTERFACE_IDX = HEADER_IDX_TO_ARG_NAME.index("interface") +HEADER_MEMBER_IDX = HEADER_IDX_TO_ARG_NAME.index("member") +HEADER_ERROR_NAME_IDX = HEADER_IDX_TO_ARG_NAME.index("error_name") +HEADER_REPLY_SERIAL_IDX = HEADER_IDX_TO_ARG_NAME.index("reply_serial") +HEADER_DESTINATION_IDX = HEADER_IDX_TO_ARG_NAME.index("destination") +HEADER_SENDER_IDX = HEADER_IDX_TO_ARG_NAME.index("sender") +HEADER_SIGNATURE_IDX = HEADER_IDX_TO_ARG_NAME.index("signature") HEADER_UNIX_FDS_IDX = HEADER_IDX_TO_ARG_NAME.index("unix_fds") +_EMPTY_HEADERS = [None] * len(HEADER_IDX_TO_ARG_NAME) + _SignatureType = SignatureType _int = int @@ -596,12 +606,12 @@ class Unmarshaller: result_list.append(reader(self, child_type)) return result_list - def _header_fields(self, header_length: _int) -> dict[str, Any]: + def _header_fields(self, header_length: _int) -> list[Any]: """Header fields are always a(yv).""" beginning_pos = self._pos - headers = {} buf = self._buf readers = self._readers + headers = _EMPTY_HEADERS.copy() while self._pos - beginning_pos < header_length: # Now read the y (byte) of struct (yv) self._pos += (-self._pos & 7) + 1 # align 8 + 1 for 'y' byte @@ -616,18 +626,19 @@ class Unmarshaller: continue token_as_int = buf[o] # Now that we have the token we can read the variant value - key = HEADER_IDX_TO_ARG_NAME[field_0] # Strings and signatures are the most common types # so we inline them for performance if token_as_int == TOKEN_O_AS_INT or token_as_int == TOKEN_S_AS_INT: - headers[key] = self._read_string_unpack() + headers[field_0] = self._read_string_unpack() elif token_as_int == TOKEN_G_AS_INT: - headers[key] = self._read_signature() + headers[field_0] = self._read_signature() else: token = buf[o : o + signature_len].decode() # There shouldn't be any other types in the header # but just in case, we'll read it using the slow path - headers[key] = readers[token](self, get_signature_tree(token).types[0]) + headers[field_0] = readers[token]( + self, get_signature_tree(token).types[0] + ) return headers def _read_header(self) -> None: @@ -694,7 +705,7 @@ class Unmarshaller: self._pos = HEADER_ARRAY_OF_STRUCT_SIGNATURE_POSITION header_fields = self._header_fields(self._header_len) self._pos += -self._pos & 7 # align 8 - signature = header_fields.pop("signature", "") + signature = header_fields[HEADER_SIGNATURE_IDX] if not self._body_len: tree = SIGNATURE_TREE_EMPTY body: list[Any] = [] @@ -749,7 +760,13 @@ class Unmarshaller: # The D-Bus implementation already validates the message, # so we don't need to do it again. validate=False, - **header_fields, + destination=header_fields[HEADER_DESTINATION_IDX], + path=header_fields[HEADER_PATH_IDX], + interface=header_fields[HEADER_INTERFACE_IDX], + member=header_fields[HEADER_MEMBER_IDX], + reply_serial=header_fields[HEADER_REPLY_SERIAL_IDX], + error_name=header_fields[HEADER_ERROR_NAME_IDX], + sender=header_fields[HEADER_SENDER_IDX], ) self._read_complete = True diff --git a/src/dbus_fast/message.py b/src/dbus_fast/message.py index 44aeb55..bbc5276 100644 --- a/src/dbus_fast/message.py +++ b/src/dbus_fast/message.py @@ -107,12 +107,12 @@ class Message: message_type: MessageType = MESSAGE_TYPE_METHOD_CALL, flags: Union[MessageFlag, int] = MESSAGE_FLAG_NONE, error_name: Optional[Union[str, ErrorType]] = None, - reply_serial: int = 0, + reply_serial: Optional[int] = None, sender: Optional[str] = None, unix_fds: list[int] = [], signature: Optional[Union[SignatureTree, str]] = None, body: list[Any] = [], - serial: int = 0, + serial: Optional[int] = None, validate: bool = True, ) -> None: self.destination = destination