Skip to content

perf: add Cython metadata parser using BytesIOReader (100's-1000's of ns improvements, x1.5-4 speedup)#814

Draft
mykaul wants to merge 1 commit intoscylladb:masterfrom
mykaul:perf/cython-metadata-parsing
Draft

perf: add Cython metadata parser using BytesIOReader (100's-1000's of ns improvements, x1.5-4 speedup)#814
mykaul wants to merge 1 commit intoscylladb:masterfrom
mykaul:perf/cython-metadata-parsing

Conversation

@mykaul
Copy link
Copy Markdown

@mykaul mykaul commented Apr 10, 2026

Summary

Add cassandra/metadata_parser.pyx with zero-copy metadata parsing using BytesIOReader, eliminating per-read bytes allocation that dominates recv_results_metadata and read_type cost in the Cython path.

Changes

  • New file: cassandra/metadata_parser.pyx — Cython cdef inline functions (read_int_br, read_short_br, read_string_br, read_type_br) operating on BytesIOReader instead of Python BytesIO. A factory function make_recv_results_metadata() returns a closure that captures the type-code map and type objects once.

  • Modified: cassandra/row_parser.pyx — Creates a single BytesIOReader from the full remaining buffer and reuses it for both metadata parsing and row parsing (previously: Python BytesIO for metadata, then a separate BytesIOReader for rows). Error recovery path correctly saves/restores reader position.

  • Modified: cassandra/protocol.pycython_protocol_handler() imports the new metadata parser and passes it to make_recv_results_rows().

Benchmarks

All measurements: taskset -c 0, Python 3.14.3, Cython compiled, origin/master vs this PR.

recv_results_metadata only (isolated)

Scenario Before After Speedup
3 simple cols 3,022 ns 582 ns 5.2x
10 simple cols 6,082 ns 1,265 ns 4.8x
50 simple cols 26,324 ns 4,882 ns 5.4x
NO_METADATA 541 ns 215 ns 2.5x
10 cols w/ collections 28,163 ns 21,832 ns 1.3x

Collections are limited by apply_parameters cost (addressed separately in #794).

recv_results_rows end-to-end (old Cython vs new Cython)

Scenario origin/master This PR Speedup
3 cols, 0 rows 5,661 ns 2,687 ns 2.04x
10 cols, 0 rows 11,068 ns 5,652 ns 1.96x
50 cols, 0 rows 44,904 ns 20,569 ns 2.18x
10 cols, 10 rows 29,590 ns 24,759 ns 1.20x
10 cols, 100 rows 181,053 ns 173,446 ns 1.04x
10 cols, 1000 rows 1,798,441 ns 1,821,257 ns 1.00x
NO_METADATA, 0 rows 5,733 ns 5,075 ns 1.13x

The improvement is entirely in metadata parsing (~2x for 0-row cases). As row count increases, metadata cost becomes a smaller fraction of total time, so the speedup dilutes. The 1000-row case is dominated by row deserialization and shows no change.

Tests

231 passed, 1 skipped — identical to baseline.

Notes

  • The prepared path (recv_prepared_metadata) is unchanged — it still uses Python BytesIO inherited from ResultMessage. Prepared statements are parsed once per PREPARE, not on the hot path.
  • setup.py automatically picks up the new .pyx file via the existing cassandra/*.pyx glob.
  • Flag constants are duplicated as compile-time DEF in the .pyx file (documented with a sync warning comment).

@mykaul mykaul force-pushed the perf/cython-metadata-parsing branch from 48538ac to 3ec7841 Compare April 10, 2026 08:18
@mykaul
Copy link
Copy Markdown
Author

mykaul commented Apr 10, 2026

I'm slowly but surely converting the Python driver to a C driver.... Unsure about this direction. :-/

@mykaul mykaul changed the title perf: add Cython metadata parser using BytesIOReader perf: add Cython metadata parser using BytesIOReader (100's of ns improvements, x1.5-4 speedup) Apr 10, 2026
@mykaul mykaul changed the title perf: add Cython metadata parser using BytesIOReader (100's of ns improvements, x1.5-4 speedup) perf: add Cython metadata parser using BytesIOReader (100's-1000's of ns improvements, x1.5-4 speedup) Apr 10, 2026
@mykaul mykaul force-pushed the perf/cython-metadata-parsing branch 4 times, most recently from d5c237e to 713bcc5 Compare April 10, 2026 08:51
Add cassandra/metadata_parser.pyx with zero-copy metadata parsing that
uses BytesIOReader instead of Python BytesIO, eliminating per-read bytes
allocation in recv_results_metadata and read_type.

Modify row_parser.pyx to create a single BytesIOReader from the full
remaining buffer and reuse it for both metadata parsing and row parsing,
instead of using Python BytesIO for metadata then a separate BytesIOReader
for rows.

Benchmarks (taskset -c 0, Python 3.14, Cython):

  recv_results_metadata only (10 simple cols): 6082 -> 1265 ns (4.8x)
  recv_results_rows (10 cols, 0 rows):        11068 -> 5652 ns (1.96x)
  recv_results_rows (10 cols, 10 rows):       29590 -> 24759 ns (1.20x)
  recv_results_rows (10 cols, 100 rows):     181053 -> 173446 ns (1.04x)
  recv_results_rows (50 cols, 0 rows):        44904 -> 20569 ns (2.18x)
@mykaul mykaul force-pushed the perf/cython-metadata-parsing branch from 713bcc5 to 1f3e38f Compare April 10, 2026 09:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant