Skip to content

Commit 514f500

Browse files
committed
perf(gap): hoist UUID-list bounds check via safe_end
Pre-compute ``safe_end = start + ((end - start) & ~(N-1))`` ahead of each 16/32/128-bit Service UUID list loop and trim the ``range`` instead of re-evaluating ``i + N <= end`` inside the loop body. The stride is a power of two for all three paths, so the bit-AND drops any malformed trailing remainder — same skip semantics, fewer per-iter ops. Pure-Python microbench (200k iters, min of 5): - 16-bit list (7 UUIDs): 4.24us -> 3.42us (-19%) - 32-bit list (3 UUIDs): 3.57us -> 3.06us (-14%) - 128-bit list (2 UUIDs): 2.27us -> 2.40us (small setup cost dominates at N=2 in pure Python; cythonized integer ops are C-level so the setup is essentially free there) Pxd: type ``safe_end`` as ``cython.uint`` alongside ``start``/``end``/``i``.
1 parent 95b0c33 commit 514f500

2 files changed

Lines changed: 29 additions & 24 deletions

File tree

src/bluetooth_data_tools/gap.pxd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ cpdef parse_advertisement_data(object data)
5353
offset=cython.uint,
5454
start=cython.uint,
5555
end=cython.uint,
56+
safe_end=cython.uint,
5657
i=cython.uint,
5758
tx_power_byte="unsigned char",
5859
uuid32_int=cython.uint,

src/bluetooth_data_tools/gap.py

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -252,32 +252,36 @@ def _uncached_parse_advertisement_bytes(
252252
service_uuids = []
253253
# Parse multiple 16-bit UUIDs (each is 2 little-endian bytes).
254254
# Decode inline to an int and look up by int key to skip the
255-
# per-iteration bytes-slice allocation.
256-
for i in range(start, end, 2):
257-
if i + 2 <= end:
258-
service_uuids.append(
259-
_cached_uint16_int_as_uuid(gap_data[i] | (gap_data[i + 1] << 8))
260-
)
255+
# per-iteration bytes-slice allocation. The stride is a power of
256+
# two so the bit-AND drops any trailing remainder for malformed
257+
# input, lifting the per-iter ``i + 2 <= end`` bounds check out
258+
# of the loop body.
259+
safe_end = start + ((end - start) & ~1)
260+
for i in range(start, safe_end, 2):
261+
service_uuids.append(
262+
_cached_uint16_int_as_uuid(gap_data[i] | (gap_data[i + 1] << 8))
263+
)
261264
elif gap_type_num in {
262265
TYPE_32BIT_SERVICE_UUID_COMPLETE,
263266
TYPE_32BIT_SERVICE_UUID_MORE_AVAILABLE,
264267
}:
265268
if service_uuids is _EMPTY_SERVICE_UUIDS:
266269
service_uuids = []
267270
# Parse multiple 32-bit UUIDs (each is 4 little-endian bytes).
268-
for i in range(start, end, 4):
269-
if i + 4 <= end:
270-
# Assemble via uint local: in Cython the shift-by-24 of an
271-
# unsigned char promotes to signed int and would yield a
272-
# negative value when bit 31 is set; assigning to a
273-
# cython.uint local recovers the unsigned 32-bit value.
274-
uuid32_int = (
275-
gap_data[i]
276-
| (gap_data[i + 1] << 8)
277-
| (gap_data[i + 2] << 16)
278-
| (gap_data[i + 3] << 24)
279-
)
280-
service_uuids.append(_cached_uint32_int_as_uuid(uuid32_int))
271+
# safe_end trims any malformed remainder ahead of the loop.
272+
safe_end = start + ((end - start) & ~3)
273+
for i in range(start, safe_end, 4):
274+
# Assemble via uint local: in Cython the shift-by-24 of an
275+
# unsigned char promotes to signed int and would yield a
276+
# negative value when bit 31 is set; assigning to a
277+
# cython.uint local recovers the unsigned 32-bit value.
278+
uuid32_int = (
279+
gap_data[i]
280+
| (gap_data[i + 1] << 8)
281+
| (gap_data[i + 2] << 16)
282+
| (gap_data[i + 3] << 24)
283+
)
284+
service_uuids.append(_cached_uint32_int_as_uuid(uuid32_int))
281285
elif gap_type_num in {
282286
TYPE_128BIT_SERVICE_UUID_MORE_AVAILABLE,
283287
TYPE_128BIT_SERVICE_UUID_COMPLETE,
@@ -287,11 +291,11 @@ def _uncached_parse_advertisement_bytes(
287291
# Parse multiple 128-bit UUIDs (each is 16 bytes). The AD length
288292
# may not be a clean multiple of 16 for malformed input — skip
289293
# any trailing remainder rather than emitting a truncated UUID.
290-
for i in range(start, end, 16):
291-
if i + 16 <= end:
292-
service_uuids.append(
293-
_cached_uint128_bytes_as_uuid(gap_data[i : i + 16])
294-
)
294+
safe_end = start + ((end - start) & ~15)
295+
for i in range(start, safe_end, 16):
296+
service_uuids.append(
297+
_cached_uint128_bytes_as_uuid(gap_data[i : i + 16])
298+
)
295299
elif gap_type_num == TYPE_SERVICE_DATA:
296300
splice_pos = start + 2
297301
if splice_pos > end:

0 commit comments

Comments
 (0)