From 24dd9d9742e8c779b9c8aa751ba8b2815b61b15b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 27 Oct 2022 13:34:32 -0500 Subject: [PATCH] feat: inline cast uint32 and int16 to speed up unmarshall (#115) --- src/dbus_fast/_private/_cython_compat.py | 4 +++ src/dbus_fast/_private/unmarshaller.pxd | 10 ++++++ src/dbus_fast/_private/unmarshaller.py | 42 ++++++++++++++++++++++-- tests/test_marshaller.py | 5 +++ 4 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 src/dbus_fast/_private/_cython_compat.py diff --git a/src/dbus_fast/_private/_cython_compat.py b/src/dbus_fast/_private/_cython_compat.py new file mode 100644 index 0000000..44756e1 --- /dev/null +++ b/src/dbus_fast/_private/_cython_compat.py @@ -0,0 +1,4 @@ +class FakeCython: + @property + def compiled(self): + return False diff --git a/src/dbus_fast/_private/unmarshaller.pxd b/src/dbus_fast/_private/unmarshaller.pxd index f475c4b..3477191 100644 --- a/src/dbus_fast/_private/unmarshaller.pxd +++ b/src/dbus_fast/_private/unmarshaller.pxd @@ -14,6 +14,8 @@ cdef unsigned int BIG_ENDIAN cdef unsigned int PROTOCOL_VERSION cdef str UINT32_CAST cdef str INT16_CAST +cdef bint SYS_IS_LITTLE_ENDIAN +cdef bint SYS_IS_BIG_ENDIAN cdef object UNPACK_HEADER_LITTLE_ENDIAN cdef object UNPACK_HEADER_BIG_ENDIAN @@ -30,6 +32,13 @@ cdef object HEADER_MESSAGE_ARG_NAME cpdef get_signature_tree +cdef inline unsigned long _cast_uint32_native(const char * payload, unsigned int offset): + cdef unsigned long *u32p = &payload[offset] + return u32p[0] + +cdef inline short _cast_int16_native(const char * payload, unsigned int offset): + cdef short *s16p = &payload[offset] + return s16p[0] cdef class MarshallerStreamEndError(Exception): pass @@ -49,6 +58,7 @@ cdef class Unmarshaller: cdef unsigned int _message_type cdef unsigned int _flag cdef unsigned int _msg_len + cdef unsigned int _is_native cdef object _uint32_unpack cdef object _int16_unpack diff --git a/src/dbus_fast/_private/unmarshaller.py b/src/dbus_fast/_private/unmarshaller.py index c3ae544..7d0a209 100644 --- a/src/dbus_fast/_private/unmarshaller.py +++ b/src/dbus_fast/_private/unmarshaller.py @@ -1,6 +1,5 @@ import array import io -import pprint import socket import sys from struct import Struct @@ -24,6 +23,9 @@ INT16_CAST = "h" INT16_SIZE = 2 INT16_DBUS_TYPE = "n" +SYS_IS_LITTLE_ENDIAN = sys.byteorder == "little" +SYS_IS_BIG_ENDIAN = sys.byteorder == "big" + DBUS_TO_CTYPE = { "y": ("B", 1), # byte INT16_DBUS_TYPE: (INT16_CAST, INT16_SIZE), # int16 @@ -99,6 +101,11 @@ class MarshallerStreamEndError(Exception): pass +try: + import cython +except ImportError: + from ._cython_compat import FakeCython as cython + # # Alignment padding is handled with the following formula below # @@ -134,6 +141,7 @@ class Unmarshaller: "_msg_len", "_uint32_unpack", "_int16_unpack", + "_is_native", ) def __init__(self, stream: io.BufferedRWPair, sock: Optional[socket.socket] = None): @@ -150,6 +158,7 @@ class Unmarshaller: self._message_type = 0 self._flag = 0 self._msg_len = 0 + self._is_native = 0 self._uint32_unpack: Callable | None = None self._int16_unpack: Callable | None = None @@ -168,6 +177,7 @@ class Unmarshaller: self._message_type = 0 self._flag = 0 self._msg_len = 0 + self._is_native = 0 self._uint32_unpack = None self._int16_unpack = None @@ -230,6 +240,10 @@ class Unmarshaller: def _read_uint32_unpack(self) -> int: self._pos += UINT32_SIZE + (-self._pos & (UINT32_SIZE - 1)) # align + if self._is_native and cython.compiled: + return _cast_uint32_native( # pragma: no cover + self._buf, self._pos - UINT32_SIZE + ) return self._uint32_unpack(self._buf, self._pos - UINT32_SIZE)[0] def read_int16_unpack(self, type_: SignatureType) -> int: @@ -237,6 +251,10 @@ class Unmarshaller: def _read_int16_unpack(self) -> int: self._pos += INT16_SIZE + (-self._pos & (INT16_SIZE - 1)) # align + if self._is_native and cython.compiled: + return _cast_int16_native( # pragma: no cover + self._buf, self._pos - INT16_SIZE + ) return self._int16_unpack(self._buf, self._pos - INT16_SIZE)[0] def read_boolean(self, type_: SignatureType) -> bool: @@ -250,7 +268,12 @@ class Unmarshaller: self._pos += UINT32_SIZE + (-self._pos & (UINT32_SIZE - 1)) # align str_start = self._pos # read terminating '\0' byte as well (str_length + 1) - self._pos += self._uint32_unpack(self._buf, str_start - UINT32_SIZE)[0] + 1 + if self._is_native and cython.compiled: + self._pos += ( # pragma: no cover + _cast_uint32_native(self._buf, str_start - UINT32_SIZE) + 1 + ) + else: + self._pos += self._uint32_unpack(self._buf, str_start - UINT32_SIZE)[0] + 1 return self._buf[str_start : self._pos - 1].decode() def read_signature(self, type_: SignatureType) -> str: @@ -302,7 +325,12 @@ class Unmarshaller: self._pos += ( -self._pos & (UINT32_SIZE - 1) ) + UINT32_SIZE # align for the uint32 - array_length = self._uint32_unpack(self._buf, self._pos - UINT32_SIZE)[0] + if self._is_native and cython.compiled: + array_length = _cast_uint32_native( # pragma: no cover + self._buf, self._pos - UINT32_SIZE + ) + else: + array_length = self._uint32_unpack(self._buf, self._pos - UINT32_SIZE)[0] child_type = type_.children[0] token = child_type.token @@ -398,6 +426,14 @@ class Unmarshaller: f"got unknown protocol version: {protocol_version}" ) + if cython.compiled and ( + (endian == LITTLE_ENDIAN and SYS_IS_LITTLE_ENDIAN) + or (endian == BIG_ENDIAN and SYS_IS_BIG_ENDIAN) + ): + self._is_native = 1 # pragma: no cover + self._body_len = _cast_uint32_native(self._buf, 4) # pragma: no cover + self._serial = _cast_uint32_native(self._buf, 8) # pragma: no cover + self._header_len = _cast_uint32_native(self._buf, 12) # pragma: no cover if endian == LITTLE_ENDIAN: ( self._body_len, diff --git a/tests/test_marshaller.py b/tests/test_marshaller.py index 00f9c2b..a87d97e 100644 --- a/tests/test_marshaller.py +++ b/tests/test_marshaller.py @@ -7,6 +7,7 @@ from unittest.mock import patch import pytest from dbus_fast import Message, MessageFlag, MessageType, SignatureTree, Variant +from dbus_fast._private._cython_compat import FakeCython from dbus_fast._private.unmarshaller import Unmarshaller @@ -171,3 +172,7 @@ def test_ay_buffer(): marshalled = msg._marshall(False) unmarshalled_msg = Unmarshaller(io.BytesIO(marshalled)).unmarshall() assert unmarshalled_msg.body[0] == body[0] + + +def tests_fallback_no_cython(): + assert FakeCython().compiled is False