Skip to content

Commit 88d006c

Browse files
committed
Merge pull request #687 from socketpair/fastxor
Weboscket XOR performance improved. Fixes #686
2 parents 0bd5a66 + 8448545 commit 88d006c

File tree

2 files changed

+53
-13
lines changed

2 files changed

+53
-13
lines changed

aiohttp/_websocket.pyx

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,48 @@
1-
from cpython cimport PyBytes_FromStringAndSize, PyBytes_AsString
2-
from cpython.ref cimport PyObject
1+
from cpython cimport PyBytes_AsString
32

3+
#from cpython cimport PyByteArray_AsString # cython still not exports that
44
cdef extern from "Python.h":
5-
char* PyByteArray_AsString(object bytearray) except NULL
5+
char* PyByteArray_AsString(bytearray ba) except NULL
66

7+
from libc.stdint cimport uint32_t, uint64_t, uintmax_t
78

89
def _websocket_mask_cython(bytes mask, bytearray data):
9-
cdef Py_ssize_t mask_len, data_len, i
10-
cdef char * in_buf
11-
cdef char * out_buf
12-
cdef char * mask_buf
13-
cdef bytes ret
14-
mask_len = len(mask)
10+
"""Note, this function mutates it's `data` argument
11+
"""
12+
cdef:
13+
Py_ssize_t data_len, i
14+
# bit operations on signed integers are implementation-specific
15+
unsigned char * in_buf
16+
const unsigned char * mask_buf
17+
uint32_t uint32_msk
18+
uint64_t uint64_msk
19+
20+
assert len(mask) == 4
21+
1522
data_len = len(data)
16-
in_buf = PyByteArray_AsString(data)
17-
mask_buf = PyBytes_AsString(mask)
23+
in_buf = <unsigned char*>PyByteArray_AsString(data)
24+
mask_buf = <const unsigned char*>PyBytes_AsString(mask)
25+
uint32_msk = (<uint32_t*>mask_buf)[0]
26+
27+
# TODO: align in_data ptr to achieve even faster speeds
28+
# does it need in python ?! malloc() always aligns to sizeof(long) bytes
29+
30+
if sizeof(uintmax_t) >= 8:
31+
uint64_msk = uint32_msk
32+
uint64_msk = (uint64_msk << 32) | uint32_msk
33+
34+
while data_len >= 8:
35+
(<uint64_t*>in_buf)[0] ^= uint64_msk
36+
in_buf += 8
37+
data_len -= 8
38+
39+
40+
while data_len >= 4:
41+
(<uint32_t*>in_buf)[0] ^= uint32_msk
42+
in_buf += 4
43+
data_len -= 4
44+
1845
for i in range(0, data_len):
19-
in_buf[i] = in_buf[i] ^ mask_buf[i % 4]
46+
in_buf[i] ^= mask_buf[i]
47+
2048
return data

aiohttp/websocket.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import hashlib
77
import os
88
import random
9+
import sys
910

1011
from struct import Struct
1112
from aiohttp import errors, hdrs
@@ -172,6 +173,9 @@ def WebSocketParser(out, buf):
172173
Message(OPCODE_BINARY, data, ''), len(data))
173174

174175

176+
native_byteorder = sys.byteorder
177+
178+
175179
def _websocket_mask_python(mask, data):
176180
"""Websocket masking function.
177181
@@ -184,7 +188,15 @@ def _websocket_mask_python(mask, data):
184188
version when available.
185189
186190
"""
187-
return bytes(b ^ mask[i % 4] for i, b in enumerate(data))
191+
assert len(mask) == 4
192+
datalen = len(data)
193+
if datalen == 0:
194+
# everything work without this, but may be changed later in Python.
195+
return b''
196+
data = int.from_bytes(data, native_byteorder)
197+
mask = int.from_bytes(mask * (datalen // 4) + mask[: datalen % 4],
198+
native_byteorder)
199+
return (data ^ mask).to_bytes(datalen, native_byteorder)
188200

189201

190202
if bool(os.environ.get('AIOHTTP_NO_EXTENSIONS')):

0 commit comments

Comments
 (0)