Skip to content

Commit 5eec7ed

Browse files
committed
perf: pre-compute int32_pack constants for null/unset markers
Replace per-call int32_pack(-1) and int32_pack(-2) with module-level _INT32_NEG1 and _INT32_NEG2 constants. Avoids redundant struct packing on every null or unset parameter in the inner write_value loop. Benchmark: ~11% speedup on the parameter serialization loop for a typical 12-param mix of values, nulls, and unsets.
1 parent 6e0eb40 commit 5eec7ed

2 files changed

Lines changed: 79 additions & 4 deletions

File tree

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"""
2+
Micro-benchmark: pre-computed int32_pack constants for null/unset markers.
3+
4+
Measures the speedup from using pre-computed _INT32_NEG1/_INT32_NEG2
5+
constants vs calling int32_pack(-1)/int32_pack(-2) per parameter.
6+
7+
Run:
8+
python benchmarks/bench_protocol_write_value.py
9+
"""
10+
import timeit
11+
import struct
12+
13+
int32_pack = struct.Struct('>i').pack
14+
15+
# Pre-computed constants (the optimization)
16+
_INT32_NEG1 = int32_pack(-1)
17+
_INT32_NEG2 = int32_pack(-2)
18+
19+
_UNSET_VALUE = object()
20+
21+
22+
def bench_write_value_constants():
23+
"""Benchmark pre-computed constants vs per-call int32_pack."""
24+
# Simulate a typical parameter list: mix of values, nulls, and unsets
25+
params = [b'\x00\x00\x00\x01'] * 8 + [None] * 3 + [_UNSET_VALUE] * 1
26+
27+
def run_precomputed():
28+
parts = []
29+
_parts_append = parts.append
30+
_i32 = int32_pack
31+
for param in params:
32+
if param is None:
33+
_parts_append(_INT32_NEG1)
34+
elif param is _UNSET_VALUE:
35+
_parts_append(_INT32_NEG2)
36+
else:
37+
_parts_append(_i32(len(param)))
38+
_parts_append(param)
39+
return b"".join(parts)
40+
41+
def run_pack_each_time():
42+
parts = []
43+
_parts_append = parts.append
44+
_i32 = int32_pack
45+
for param in params:
46+
if param is None:
47+
_parts_append(_i32(-1))
48+
elif param is _UNSET_VALUE:
49+
_parts_append(_i32(-2))
50+
else:
51+
_parts_append(_i32(len(param)))
52+
_parts_append(param)
53+
return b"".join(parts)
54+
55+
# Verify identical output
56+
assert run_precomputed() == run_pack_each_time()
57+
58+
n = 500_000
59+
t_precomputed = timeit.timeit(run_precomputed, number=n)
60+
t_pack = timeit.timeit(run_pack_each_time, number=n)
61+
62+
print(f"Pre-computed constants ({n} iters, {len(params)} params): "
63+
f"{t_precomputed:.3f}s ({t_precomputed / n * 1e6:.2f} us/call)")
64+
print(f"Pack each time ({n} iters, {len(params)} params): "
65+
f"{t_pack:.3f}s ({t_pack / n * 1e6:.2f} us/call)")
66+
speedup = t_pack / t_precomputed
67+
print(f"Speedup: {speedup:.2f}x")
68+
69+
70+
if __name__ == '__main__':
71+
bench_write_value_constants()

cassandra/protocol.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ class InternalError(Exception):
6969

7070
_UNSET_VALUE = object()
7171

72+
# Pre-computed packed constants for null/unset markers
73+
_INT32_NEG1 = int32_pack(-1) # null value marker
74+
_INT32_NEG2 = int32_pack(-2) # unset value marker
75+
7276

7377
def register_class(cls):
7478
_message_types_by_opcode[cls.opcode] = cls
@@ -594,9 +598,9 @@ def _write_query_params(self, f, protocol_version):
594598
_parts_append = parts.append
595599
for param in self.query_params:
596600
if param is None:
597-
_parts_append(_int32_pack(-1))
601+
_parts_append(_INT32_NEG1)
598602
elif param is _UNSET_VALUE:
599-
_parts_append(_int32_pack(-2))
603+
_parts_append(_INT32_NEG2)
600604
else:
601605
_parts_append(_int32_pack(len(param)))
602606
_parts_append(param)
@@ -943,9 +947,9 @@ def send_body(self, f, protocol_version):
943947
_p(_u16(len(params)))
944948
for param in params:
945949
if param is None:
946-
_p(_i32(-1))
950+
_p(_INT32_NEG1)
947951
elif param is _UNSET_VALUE:
948-
_p(_i32(-2))
952+
_p(_INT32_NEG2)
949953
else:
950954
if isinstance(param, str):
951955
param = param.encode('utf8')

0 commit comments

Comments
 (0)