Skip to content

(improvement) serializers: add Cython-optimized serialization for VectorType#748

Draft
mykaul wants to merge 7 commits intoscylladb:masterfrom
mykaul:perf/cython-serializers
Draft

(improvement) serializers: add Cython-optimized serialization for VectorType#748
mykaul wants to merge 7 commits intoscylladb:masterfrom
mykaul:perf/cython-serializers

Conversation

@mykaul
Copy link
Copy Markdown

@mykaul mykaul commented Mar 14, 2026

Summary

Adds cassandra/serializers.pyx and cassandra/serializers.pxd implementing Cython-optimized serialization that mirrors the deserializers.pyx architecture.

What's included

  • Scalar serializers: SerFloatType (4-byte IEEE 754), SerDoubleType (8-byte), SerInt32Type (4-byte signed) — the three subtypes commonly used in vector columns
  • SerVectorType: Pre-allocates a contiguous char * buffer and uses C-level byte swapping for float/double/int32 vectors, with a generic fallback for other subtypes
  • Buffer fast-path for NumPy arrays: When the input is a NumPy array with a matching dtype, Cython typed memoryviews (float[::1], double[::1], int[::1]) read directly from the buffer protocol — bypassing Python object creation entirely. Falls through to the element-wise path on dtype mismatch.
  • GenericSerializer: Delegates to the Python-level cqltype.serialize() classmethod for all other types
  • Factory functions: find_serializer(cqltype) and make_serializers(cqltypes_list) for easy lookup and batch creation

Architecture

Mirrors deserializers.pyx exactly:

Deserializer side Serializer side
Deserializer base class Serializer base class
DesFloatType, DesDoubleType, DesInt32Type SerFloatType, SerDoubleType, SerInt32Type
DesVectorType (type-specialized) SerVectorType (type-specialized)
GenericDeserializer GenericSerializer
find_deserializer() find_serializer()
make_deserializers() make_serializers()

Performance

List/tuple inputs — Cython vs Python baseline

Benchmarked on Vector<float, 1536> (typical embedding dimension):

Method us/op Speedup
Current VectorType.serialize() (io.BytesIO loop) ~823 us 1x (baseline)
Python struct.pack batch format string ~74 us ~11x
Cython SerVectorType ~4 us ~30x

NumPy array inputs — buffer fast-path

When inputs are NumPy arrays with matching dtype, the buffer-protocol fast-path avoids per-element Python object creation entirely:

Vector type Element-wise (us/op) Buffer fast-path (us/op) Speedup
Vector<float, 1536> (numpy float32) 140.88 us 1.51 us 93x
Vector<double, 768> (numpy float64) 45.54 us 1.61 us 28x
Vector<int, 1536> (numpy int32) 197.89 us 1.01 us 196x

List/tuple and scalar paths are unaffected (no regressions):

Benchmark Before buffer path After buffer path
Vector float 1536 (list) 13.79 us 15.90 us
Scalar float/double/int32 0.12–0.17 us 0.06–0.12 us

No setup.py changes needed — the existing cassandra/*.pyx glob already picks up new .pyx files.

Related PRs

Pre-review checklist

  • I have split my patch into logically separate commits.
  • All commit messages clearly explain what they change and why.
  • I added relevant tests for new features and bug fixes.
  • All commits compile, pass static checks and pass test.
  • PR description sums up the changes and reasons why they should be introduced.
  • I have provided docstrings for the public items that I want to introduce.
  • I have adjusted the documentation in ./docs/source/.
  • I added appropriate Fixes: annotations to PR description.

mykaul added a commit to mykaul/python-driver that referenced this pull request Mar 14, 2026
…nt.bind()

When Cython serializers (from cassandra.serializers) are available and no
column encryption policy is active, BoundStatement.bind() now uses
pre-built Serializer objects cached on the PreparedStatement instead of
calling cqltype classmethods. This avoids per-value Python method dispatch
overhead and enables the ~30x vector serialization speedup from the Cython
serializers module.

The bind loop is split into three paths:
1. Column encryption policy path (unchanged behavior)
2. Cython serializers path (new fast path)
3. Plain Python path (no CE, no Cython -- removes per-value ColDesc/CE check)

Depends on PR scylladb#748 (Cython serializers module) and PR scylladb#630 (CE-policy
bind split).
@mykaul mykaul marked this pull request as draft March 14, 2026 11:23
@mykaul mykaul requested a review from Copilot March 14, 2026 19:25
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new Cython extension module to accelerate CQL value serialization—especially VectorType—using the same general “typed Serializer object + factory lookup” approach as the existing Cython deserialization stack.

Changes:

  • Add cassandra/serializers.pyx implementing Cython serializers for FloatType, DoubleType, Int32Type, and an optimized VectorType serializer with generic fallback.
  • Add find_serializer() / make_serializers() factory helpers for serializer creation.
  • Add cassandra/serializers.pxd to expose the Serializer interface to other Cython modules.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 8 comments.

File Description
cassandra/serializers.pyx New Cython-optimized serialization implementations and factory lookup.
cassandra/serializers.pxd Cython declarations for the Serializer interface.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

mykaul added a commit to mykaul/python-driver that referenced this pull request Mar 16, 2026
…nt.bind()

When Cython serializers (from cassandra.serializers) are available and no
column encryption policy is active, BoundStatement.bind() now uses
pre-built Serializer objects cached on the PreparedStatement instead of
calling cqltype classmethods. This avoids per-value Python method dispatch
overhead and enables the ~30x vector serialization speedup from the Cython
serializers module.

The bind loop is split into three paths:
1. Column encryption policy path (unchanged behavior)
2. Cython serializers path (new fast path)
3. Plain Python path (no CE, no Cython -- removes per-value ColDesc/CE check)

Depends on PR scylladb#748 (Cython serializers module) and PR scylladb#630 (CE-policy
bind split).
mykaul added a commit to mykaul/python-driver that referenced this pull request Mar 19, 2026
…lizers

Address all 8 Copilot review comments on PR scylladb#748:

- Add _check_float_range() for float overflow detection matching struct.pack
- Add _check_int32_range() for int32 bounds checking before C cast
- Wire bounds checks into SerFloatType, SerInt32Type, and VectorType fast-paths
- Replace malloc/free with PyBytes_FromStringAndSize(NULL,n)+PyBytes_AS_STRING
- Add empty vector early return (b'') before allocation
- Remove unused uint32_t cimport and libc.stdlib import
- Add comprehensive test suite (67 tests) covering equivalence, overflow,
  special values, vectors, round-trips, and factory functions
@mykaul mykaul requested a review from Copilot March 19, 2026 14:19
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Cython serialization module to speed up VectorType (and a few common scalar subtypes) while keeping wire-format output identical to existing cqltypes.*.serialize() implementations.

Changes:

  • Introduce cassandra/serializers.pyx + .pxd implementing Serializer classes, including a specialized SerVectorType with float/double/int32 fast-paths.
  • Add serializer lookup/batch factories (find_serializer, make_serializers).
  • Add unit tests validating byte-for-byte equivalence and basic round-trips for the new serializers.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 7 comments.

File Description
cassandra/serializers.pyx New Cython serializers, including optimized VectorType serialization and factory lookup functions.
cassandra/serializers.pxd Cython declarations for the Serializer base class.
tests/unit/test_serializers.py New unit tests covering scalar/vector equivalence, round-trips, and factory behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

mykaul added a commit to mykaul/python-driver that referenced this pull request Mar 20, 2026
…nt.bind()

When Cython serializers (from cassandra.serializers) are available and no
column encryption policy is active, BoundStatement.bind() now uses
pre-built Serializer objects cached on the PreparedStatement instead of
calling cqltype classmethods. This avoids per-value Python method dispatch
overhead and enables the ~30x vector serialization speedup from the Cython
serializers module.

The bind loop is split into three paths:
1. Column encryption policy path (unchanged behavior)
2. Cython serializers path (new fast path)
3. Plain Python path (no CE, no Cython -- removes per-value ColDesc/CE check)

Depends on PR scylladb#748 (Cython serializers module) and PR scylladb#630 (CE-policy
bind split).
@mykaul mykaul requested a review from Copilot March 24, 2026 22:14
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Cython extension module (cassandra.serializers) that provides optimized serialization implementations for common scalar types and VectorType, intended to mirror the existing deserializers.pyx architecture and improve vector-heavy workloads.

Changes:

  • Introduces cassandra/serializers.pyx + .pxd implementing Serializer classes, SerVectorType, and factory helpers (find_serializer, make_serializers).
  • Adds unit tests validating byte-for-byte equivalence vs. Python cqltype.serialize() and basic factory behavior.
  • Implements float/double/int32 vector fast-paths via preallocated contiguous buffers and endian swapping.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.

File Description
cassandra/serializers.pyx New Cython-optimized serializers, including VectorType specialized paths and serializer lookup/factory functions.
cassandra/serializers.pxd Cython declarations for the Serializer base class API.
tests/unit/test_serializers.py New tests for serializer equivalence, edge cases, and factory helpers under Cython.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

mykaul added 4 commits March 25, 2026 13:47
…torType

Add cassandra/serializers.pyx and cassandra/serializers.pxd implementing
Cython-optimized serialization that mirrors the deserializers.pyx architecture.

Implements type-specialized serializers for the three subtypes commonly used
in vector columns:
- SerFloatType: 4-byte big-endian IEEE 754 float
- SerDoubleType: 8-byte big-endian double
- SerInt32Type: 4-byte big-endian signed int32

SerVectorType pre-allocates a contiguous buffer and uses C-level byte swapping
for float/double/int32 vectors, with a generic fallback for other subtypes.
GenericSerializer delegates to the Python-level cqltype.serialize() classmethod.

Factory functions find_serializer() and make_serializers() allow easy lookup
and batch creation of serializers for column types.

Benchmarks show ~30x speedup over the current io.BytesIO baseline and ~3x
speedup over Python struct.pack for Vector<float, 1536> serialization.

No setup.py changes needed - the existing cassandra/*.pyx glob already picks
up new .pyx files.
…lizers

Address all 8 Copilot review comments on PR scylladb#748:

- Add _check_float_range() for float overflow detection matching struct.pack
- Add _check_int32_range() for int32 bounds checking before C cast
- Wire bounds checks into SerFloatType, SerInt32Type, and VectorType fast-paths
- Replace malloc/free with PyBytes_FromStringAndSize(NULL,n)+PyBytes_AS_STRING
- Add empty vector early return (b'') before allocation
- Remove unused uint32_t cimport and libc.stdlib import
- Add comprehensive test suite (67 tests) covering equivalence, overflow,
  special values, vectors, round-trips, and factory functions
- Fix _check_float_range() docstring: clarify it raises OverflowError, not struct.error
- Fix _check_int32_range() docstring: same clarification
- Document __getitem__ requirement in vector fast-paths (_serialize_float,
  _serialize_double, _serialize_int32) as intentional for performance
- Expand test import guard to cover VERIFY_CYTHON
- Remove unused imports: math, parse_casstype_args
- Guard find_serializer() against un-parameterized VectorType base class
  that would crash _is_float_type(None) with TypeError
- Fix assertAlmostEqual(inf, inf) test bug (inf - inf = nan fails)
- Wrap test serializer import in try/except for graceful skip when .so
  is missing under VERIFY_CYTHON
- Change make_serializers() to return obj_array (Cython typed memoryview)
  matching make_deserializers() convention
- Eliminate double values[i] indexing in _serialize_float vector loop
- Remove unused ctypes import in test_flt_max
@mykaul mykaul force-pushed the perf/cython-serializers branch 2 times, most recently from 91fe2ed to 91cd1ef Compare March 25, 2026 21:43
- Fix float range check: use cast-to-float + isinf check instead of
  FLT_MAX comparison, matching struct.pack('>f') rounding semantics
  (e.g. 3.4028235e38 now correctly accepted)
- Normalize vector input with tuple() so non-subscriptable iterables
  with __len__ + __iter__ work (matches Python VectorType.serialize)
- Change vector_size from int to Py_ssize_t to prevent overflow
- Fix int32 vector double-indexing: store values[i] in local variable
- Update docstrings to reflect actual behavior
- Add regression tests for float boundary rounding and iterable input
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

mykaul added 2 commits March 26, 2026 12:12
- Support __index__ protocol in int32 serialization (_coerce_int helper)
- Add empty-list guard to deserializers.obj_array() for consistency
- Move test file to tests/unit/cython/test_serializers.py, use shared cythontest
- Skip tuple() copy when value is already list or tuple (perf optimization)
- Add test_index_protocol and test_vector_index_protocol tests
- Update docstrings for obj_array() in both serializers and deserializers
@mykaul
Copy link
Copy Markdown
Author

mykaul commented Mar 26, 2026

CI flakiness above - fix sent - #767

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.

2 participants