Skip to content

Commit

Permalink
feat: inline cast uint32 and int16 to speed up unmarshall (#115)
Browse files Browse the repository at this point in the history
  • Loading branch information
bdraco authored Oct 27, 2022
1 parent 5c2826d commit 24dd9d9
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 3 deletions.
4 changes: 4 additions & 0 deletions src/dbus_fast/_private/_cython_compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class FakeCython:
@property
def compiled(self):
return False
10 changes: 10 additions & 0 deletions src/dbus_fast/_private/unmarshaller.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 = <unsigned long *> &payload[offset]
return u32p[0]

cdef inline short _cast_int16_native(const char * payload, unsigned int offset):
cdef short *s16p = <short *> &payload[offset]
return s16p[0]

cdef class MarshallerStreamEndError(Exception):
pass
Expand All @@ -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

Expand Down
42 changes: 39 additions & 3 deletions src/dbus_fast/_private/unmarshaller.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import array
import io
import pprint
import socket
import sys
from struct import Struct
Expand All @@ -24,6 +23,9 @@
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
Expand Down Expand Up @@ -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
#
Expand Down Expand Up @@ -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):
Expand All @@ -150,6 +158,7 @@ def __init__(self, stream: io.BufferedRWPair, sock: Optional[socket.socket] = No
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

Expand All @@ -168,6 +177,7 @@ def reset(self) -> None:
self._message_type = 0
self._flag = 0
self._msg_len = 0
self._is_native = 0
self._uint32_unpack = None
self._int16_unpack = None

Expand Down Expand Up @@ -230,13 +240,21 @@ def read_uint32_unpack(self, type_: SignatureType) -> int:

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:
return self._read_int16_unpack()

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:
Expand All @@ -250,7 +268,12 @@ def _read_string_unpack(self) -> str:
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:
Expand Down Expand Up @@ -302,7 +325,12 @@ def _read_array(self, type_: SignatureType) -> Iterable[Any]:
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
Expand Down Expand Up @@ -398,6 +426,14 @@ def _read_header(self) -> None:
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,
Expand Down
5 changes: 5 additions & 0 deletions tests/test_marshaller.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
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


Expand Down Expand Up @@ -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

0 comments on commit 24dd9d9

Please sign in to comment.