@@ -219,12 +219,7 @@ cdef class SerVectorType(Serializer):
219219 self .type_code = 0
220220
221221 cpdef bytes serialize(self , object value, int protocol_version):
222- # Normalize to tuple/list so indexing works for any iterable.
223- # The Python VectorType.serialize() only requires len() + iteration,
224- # so we must accept the same inputs. Avoid a copy if value is
225- # already a list or tuple (common fast path for embeddings).
226- if not isinstance (value, (list , tuple )):
227- value = tuple (value)
222+ cdef object result
228223 cdef Py_ssize_t v_length = len (value)
229224 if v_length != self .vector_size:
230225 raise ValueError (
@@ -233,6 +228,28 @@ cdef class SerVectorType(Serializer):
233228 self .vector_size, self .subtype.typename,
234229 self .vector_size, v_length))
235230
231+ if self .type_code == 1 :
232+ result = self ._serialize_float_buffer(value)
233+ if result is not None :
234+ return result
235+ elif self .type_code == 2 :
236+ result = self ._serialize_double_buffer(value)
237+ if result is not None :
238+ return result
239+ elif self .type_code == 3 :
240+ result = self ._serialize_int32_buffer(value)
241+ if result is not None :
242+ return result
243+
244+ # Keep indexable sequences on the fast path. Fall back to tuple()
245+ # only for iterable-only inputs so the Cython path still matches
246+ # Python VectorType.serialize() input semantics.
247+ if v_length != 0 and not isinstance (value, (list , tuple )):
248+ try :
249+ value[0 ]
250+ except (TypeError , KeyError , IndexError ):
251+ value = tuple (value)
252+
236253 if self .type_code == 1 :
237254 return self ._serialize_float(value)
238255 elif self .type_code == 2 :
@@ -245,8 +262,8 @@ cdef class SerVectorType(Serializer):
245262 cdef inline bytes _serialize_float(self , object values):
246263 """ Serialize a list of floats into a contiguous big-endian buffer.
247264
248- ``values`` is already a tuple (normalized in ``serialize()``), so
249- indexing is always safe and fast.
265+ ``values`` is already an indexable sequence (normalized in
266+ ``serialize()``), so integer indexing is safe and fast.
250267 """
251268 cdef Py_ssize_t i
252269 cdef Py_ssize_t buf_size = self .vector_size * 4
@@ -277,11 +294,54 @@ cdef class SerVectorType(Serializer):
277294
278295 return result
279296
297+ cdef inline object _serialize_float_buffer(self , object values):
298+ """ Fast path for contiguous float32 buffers (e.g. numpy float32 arrays).
299+
300+ No ``_check_float_range`` is needed: the typed memoryview
301+ ``float[::1]`` constrains values to C float (IEEE 754 float32),
302+ so overflow is impossible by definition. Returns ``None`` when
303+ *values* does not support the buffer protocol with the required
304+ format, letting the caller fall through to the element-wise path.
305+ """
306+ cdef float [::1 ] view
307+ cdef Py_ssize_t buf_size = self .vector_size * 4
308+ cdef Py_ssize_t i
309+ cdef object result
310+ cdef char * buf
311+ cdef char * dst
312+ cdef char * src
313+ cdef float val
314+
315+ try :
316+ view = values
317+ except (TypeError , ValueError ):
318+ return None
319+
320+ if buf_size == 0 :
321+ return b" "
322+
323+ result = PyBytes_FromStringAndSize(NULL , buf_size)
324+ buf = PyBytes_AS_STRING(result)
325+
326+ if is_little_endian:
327+ for i in range (self .vector_size):
328+ val = view[i]
329+ src = < char * > & val
330+ dst = buf + i * 4
331+ dst[0 ] = src[3 ]
332+ dst[1 ] = src[2 ]
333+ dst[2 ] = src[1 ]
334+ dst[3 ] = src[0 ]
335+ else :
336+ memcpy(buf, & view[0 ], buf_size)
337+
338+ return result
339+
280340 cdef inline bytes _serialize_double(self , object values):
281341 """ Serialize a list of doubles into a contiguous big-endian buffer.
282342
283- ``values`` is already a tuple (normalized in ``serialize()``), so
284- indexing is always safe and fast.
343+ ``values`` is already an indexable sequence (normalized in
344+ ``serialize()``), so integer indexing is safe and fast.
285345 """
286346 cdef Py_ssize_t i
287347 cdef Py_ssize_t buf_size = self .vector_size * 8
@@ -313,11 +373,55 @@ cdef class SerVectorType(Serializer):
313373
314374 return result
315375
376+ cdef inline object _serialize_double_buffer(self , object values):
377+ """ Fast path for contiguous float64 buffers (e.g. numpy float64 arrays).
378+
379+ Returns ``None`` when *values* does not expose a compatible
380+ buffer, letting the caller fall through to the element-wise path.
381+ """
382+ cdef double [::1 ] view
383+ cdef Py_ssize_t buf_size = self .vector_size * 8
384+ cdef Py_ssize_t i
385+ cdef object result
386+ cdef char * buf
387+ cdef char * dst
388+ cdef char * src
389+ cdef double val
390+
391+ try :
392+ view = values
393+ except (TypeError , ValueError ):
394+ return None
395+
396+ if buf_size == 0 :
397+ return b" "
398+
399+ result = PyBytes_FromStringAndSize(NULL , buf_size)
400+ buf = PyBytes_AS_STRING(result)
401+
402+ if is_little_endian:
403+ for i in range (self .vector_size):
404+ val = view[i]
405+ src = < char * > & val
406+ dst = buf + i * 8
407+ dst[0 ] = src[7 ]
408+ dst[1 ] = src[6 ]
409+ dst[2 ] = src[5 ]
410+ dst[3 ] = src[4 ]
411+ dst[4 ] = src[3 ]
412+ dst[5 ] = src[2 ]
413+ dst[6 ] = src[1 ]
414+ dst[7 ] = src[0 ]
415+ else :
416+ memcpy(buf, & view[0 ], buf_size)
417+
418+ return result
419+
316420 cdef inline bytes _serialize_int32(self , object values):
317421 """ Serialize a list of int32 values into a contiguous big-endian buffer.
318422
319- ``values`` is already a tuple (normalized in ``serialize()``), so
320- indexing is always safe and fast.
423+ ``values`` is already an indexable sequence (normalized in
424+ ``serialize()``), so integer indexing is safe and fast.
321425 """
322426 cdef Py_ssize_t i
323427 cdef Py_ssize_t buf_size = self .vector_size * 4
@@ -348,6 +452,47 @@ cdef class SerVectorType(Serializer):
348452
349453 return result
350454
455+ cdef inline object _serialize_int32_buffer(self , object values):
456+ """ Fast path for contiguous int32 buffers (e.g. numpy int32 arrays).
457+
458+ No ``_coerce_int`` / ``_check_int32_range`` is needed: the typed
459+ memoryview ``int[::1]`` enforces 32-bit signed integer range at
460+ the buffer-protocol level. Returns ``None`` when *values* does
461+ not expose a compatible buffer, letting the caller fall through
462+ to the element-wise path.
463+ """
464+ cdef int [::1 ] view
465+ cdef Py_ssize_t buf_size = self .vector_size * 4
466+ cdef Py_ssize_t i
467+ cdef object result
468+ cdef char * buf
469+ cdef char * dst
470+ cdef char * src
471+
472+ try :
473+ view = values
474+ except (TypeError , ValueError ):
475+ return None
476+
477+ if buf_size == 0 :
478+ return b" "
479+
480+ result = PyBytes_FromStringAndSize(NULL , buf_size)
481+ buf = PyBytes_AS_STRING(result)
482+
483+ if is_little_endian:
484+ for i in range (self .vector_size):
485+ src = < char * > & view[i]
486+ dst = buf + i * 4
487+ dst[0 ] = src[3 ]
488+ dst[1 ] = src[2 ]
489+ dst[2 ] = src[1 ]
490+ dst[3 ] = src[0 ]
491+ else :
492+ memcpy(buf, & view[0 ], buf_size)
493+
494+ return result
495+
351496 cdef inline bytes _serialize_generic(self , object values, int protocol_version):
352497 """ Fallback: element-by-element Python serialization for non-optimized types."""
353498 import io
0 commit comments