Skip to content

Commit 65d2a59

Browse files
committed
Refactor Arrow numpy writers: extract base class, unify bool writer, optimize null dims
- Extract ListFieldWriterBase with shared reserve/writeNull/finish/m_listBuilder - Add ArrowArrayType<T> trait (bool -> uint8_t), remove makeBoolListItemsWriter - NumpyNDArrayWriter: write null dims when shape matches previous row - NumpyNDArrayReader: reuse last-seen dims on null Signed-off-by: Arham Chopra <arham.chopra@cubistsystematic.com>
1 parent 98149c5 commit 65d2a59

2 files changed

Lines changed: 73 additions & 132 deletions

File tree

cpp/csp/python/adapters/ArrowNumpyListReader.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -356,10 +356,10 @@ class NumpyNDArrayReader final : public csp::adapters::arrow::TypedFieldReader<D
356356
auto arrayValue = m_readValue( *m_column, row );
357357

358358
if( m_dimsColumn -> IsValid( row ) )
359-
{
360-
auto dims = readDimsFromListCell( *m_dimsColumn, row );
361-
arrayValue = m_reshapeCallback( std::move( arrayValue ), dims );
362-
}
359+
m_lastDims = readDimsFromListCell( *m_dimsColumn, row );
360+
361+
if( !m_lastDims.empty() )
362+
arrayValue = m_reshapeCallback( std::move( arrayValue ), m_lastDims );
363363

364364
out = std::move( arrayValue );
365365
return true;
@@ -373,6 +373,7 @@ class NumpyNDArrayReader final : public csp::adapters::arrow::TypedFieldReader<D
373373
std::function<DialectGenericType( const ::arrow::Array &, int64_t )> m_readValue;
374374
ReshapeCallback m_reshapeCallback;
375375
const ::arrow::Array * m_dimsColumn;
376+
std::vector<int64_t> m_lastDims;
376377
};
377378

378379
} // namespace numpy

cpp/csp/python/adapters/ArrowNumpyListWriter.h

Lines changed: 68 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,38 @@ namespace numpy
3434
#define ARROW_OK_OR_THROW_WRITER( expr, msg ) \
3535
do { auto __s = ( expr ); if( !__s.ok() ) CSP_THROW( RuntimeException, msg << ": " << __s.ToString() ); } while(0)
3636

37+
// --- Base class for list writers that share reserve/writeNull/finish on a single ListBuilder ---
38+
39+
class ListFieldWriterBase : public csp::adapters::arrow::FieldWriter
40+
{
41+
public:
42+
ListFieldWriterBase( std::vector<std::string> columnNames,
43+
std::vector<std::shared_ptr<::arrow::DataType>> dataTypes,
44+
const StructFieldPtr & structField )
45+
: FieldWriter( std::move( columnNames ), std::move( dataTypes ), structField )
46+
{}
47+
48+
void reserve( int64_t numRows ) override
49+
{
50+
ARROW_OK_OR_THROW_WRITER( m_listBuilder -> Reserve( numRows ), "Failed to reserve list builder" );
51+
}
52+
53+
void writeNull() override
54+
{
55+
ARROW_OK_OR_THROW_WRITER( m_listBuilder -> AppendNull(), "Failed to append null list" );
56+
}
57+
58+
std::vector<std::shared_ptr<::arrow::Array>> finish() override
59+
{
60+
std::shared_ptr<::arrow::Array> arr;
61+
ARROW_OK_OR_THROW_WRITER( m_listBuilder -> Finish( &arr ), "Failed to finish list array" );
62+
return { arr };
63+
}
64+
65+
protected:
66+
std::shared_ptr<::arrow::ListBuilder> m_listBuilder;
67+
};
68+
3769
// --- Native element writers (double, int64, bool) ---
3870
// Use bulk AppendValues for a single resize + copy instead of per-element Append.
3971

@@ -58,34 +90,17 @@ inline void writeNativeElements<bool, ::arrow::BooleanBuilder>(
5890
// --- Native list writer ---
5991

6092
template<typename CspT, typename ArrowBuilderT>
61-
class NativeListWriter final : public csp::adapters::arrow::FieldWriter
93+
class NativeListWriter final : public ListFieldWriterBase
6294
{
6395
public:
6496
NativeListWriter( const std::string & columnName, const StructFieldPtr & structField,
6597
std::shared_ptr<::arrow::DataType> elementType )
66-
: FieldWriter( { columnName }, { ::arrow::list( elementType ) }, structField )
98+
: ListFieldWriterBase( { columnName }, { ::arrow::list( elementType ) }, structField )
6799
{
68100
m_valueBuilder = std::make_shared<ArrowBuilderT>();
69101
m_listBuilder = std::make_shared<::arrow::ListBuilder>( ::arrow::default_memory_pool(), m_valueBuilder );
70102
}
71103

72-
void reserve( int64_t numRows ) override
73-
{
74-
ARROW_OK_OR_THROW_WRITER( m_listBuilder -> Reserve( numRows ), "Failed to reserve list builder" );
75-
}
76-
77-
void writeNull() override
78-
{
79-
ARROW_OK_OR_THROW_WRITER( m_listBuilder -> AppendNull(), "Failed to append null list" );
80-
}
81-
82-
std::vector<std::shared_ptr<::arrow::Array>> finish() override
83-
{
84-
std::shared_ptr<::arrow::Array> arr;
85-
ARROW_OK_OR_THROW_WRITER( m_listBuilder -> Finish( &arr ), "Failed to finish list array" );
86-
return { arr };
87-
}
88-
89104
protected:
90105
void doWrite( const Struct * s ) override
91106
{
@@ -102,47 +117,27 @@ class NativeListWriter final : public csp::adapters::arrow::FieldWriter
102117
}
103118
else
104119
{
105-
PyObjectPtr contiguousOwner{ PyObjectPtr::own(
106-
reinterpret_cast<PyObject *>( PyArray_GETCONTIGUOUS( pyArr ) ) ) };
107-
writeNativeElements<CspT, ArrowBuilderT>( m_valueBuilder.get(),
108-
reinterpret_cast<PyArrayObject *>( contiguousOwner.get() ), len );
120+
PyObjectPtr contiguousOwner{ PyObjectPtr::own( reinterpret_cast<PyObject *>( PyArray_GETCONTIGUOUS( pyArr ) ) ) };
121+
writeNativeElements<CspT, ArrowBuilderT>( m_valueBuilder.get(), reinterpret_cast<PyArrayObject *>( contiguousOwner.get() ), len );
109122
}
110123
}
111124

112125
private:
113126
std::shared_ptr<ArrowBuilderT> m_valueBuilder;
114-
std::shared_ptr<::arrow::ListBuilder> m_listBuilder;
115127
};
116128

117129
// --- String list writer ---
118130

119-
class StringListWriter final : public csp::adapters::arrow::FieldWriter
131+
class StringListWriter final : public ListFieldWriterBase
120132
{
121133
public:
122134
StringListWriter( const std::string & columnName, const StructFieldPtr & structField )
123-
: FieldWriter( { columnName }, { ::arrow::list( ::arrow::utf8() ) }, structField )
135+
: ListFieldWriterBase( { columnName }, { ::arrow::list( ::arrow::utf8() ) }, structField )
124136
{
125137
m_valueBuilder = std::make_shared<::arrow::StringBuilder>();
126138
m_listBuilder = std::make_shared<::arrow::ListBuilder>( ::arrow::default_memory_pool(), m_valueBuilder );
127139
}
128140

129-
void reserve( int64_t numRows ) override
130-
{
131-
ARROW_OK_OR_THROW_WRITER( m_listBuilder -> Reserve( numRows ), "Failed to reserve string list builder" );
132-
}
133-
134-
void writeNull() override
135-
{
136-
ARROW_OK_OR_THROW_WRITER( m_listBuilder -> AppendNull(), "Failed to append null list" );
137-
}
138-
139-
std::vector<std::shared_ptr<::arrow::Array>> finish() override
140-
{
141-
std::shared_ptr<::arrow::Array> arr;
142-
ARROW_OK_OR_THROW_WRITER( m_listBuilder -> Finish( &arr ), "Failed to finish string list array" );
143-
return { arr };
144-
}
145-
146141
protected:
147142
void doWrite( const Struct * s ) override
148143
{
@@ -181,7 +176,6 @@ class StringListWriter final : public csp::adapters::arrow::FieldWriter
181176

182177
private:
183178
std::shared_ptr<::arrow::StringBuilder> m_valueBuilder;
184-
std::shared_ptr<::arrow::ListBuilder> m_listBuilder;
185179
std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> m_converter; // reused across rows
186180
std::string m_utf8Buf; // reused buffer
187181
};
@@ -191,34 +185,17 @@ class StringListWriter final : public csp::adapters::arrow::FieldWriter
191185
// Writes numpy datetime64/timedelta64 arrays into Arrow List<Timestamp(ns,"UTC")> or List<Duration(ns)> columns.
192186
// Detects the numpy datetime unit at write time and converts to nanoseconds using scalingFromNumpyDtUnit().
193187
template<typename ArrowBuilderT>
194-
class TemporalListWriter final : public csp::adapters::arrow::FieldWriter
188+
class TemporalListWriter final : public ListFieldWriterBase
195189
{
196190
public:
197191
TemporalListWriter( const std::string & columnName, const StructFieldPtr & structField,
198192
std::shared_ptr<::arrow::DataType> elementType )
199-
: FieldWriter( { columnName }, { ::arrow::list( elementType ) }, structField )
193+
: ListFieldWriterBase( { columnName }, { ::arrow::list( elementType ) }, structField )
200194
{
201195
m_valueBuilder = std::make_shared<ArrowBuilderT>( elementType, ::arrow::default_memory_pool() );
202196
m_listBuilder = std::make_shared<::arrow::ListBuilder>( ::arrow::default_memory_pool(), m_valueBuilder );
203197
}
204198

205-
void reserve( int64_t numRows ) override
206-
{
207-
ARROW_OK_OR_THROW_WRITER( m_listBuilder -> Reserve( numRows ), "Failed to reserve temporal list builder" );
208-
}
209-
210-
void writeNull() override
211-
{
212-
ARROW_OK_OR_THROW_WRITER( m_listBuilder -> AppendNull(), "Failed to append null temporal list" );
213-
}
214-
215-
std::vector<std::shared_ptr<::arrow::Array>> finish() override
216-
{
217-
std::shared_ptr<::arrow::Array> arr;
218-
ARROW_OK_OR_THROW_WRITER( m_listBuilder -> Finish( &arr ), "Failed to finish temporal list array" );
219-
return { arr };
220-
}
221-
222199
protected:
223200
void doWrite( const Struct * s ) override
224201
{
@@ -233,8 +210,7 @@ class TemporalListWriter final : public csp::adapters::arrow::FieldWriter
233210
PyObjectPtr contiguousOwner;
234211
if( !PyArray_IS_C_CONTIGUOUS( pyArr ) )
235212
{
236-
contiguousOwner = PyObjectPtr::own(
237-
reinterpret_cast<PyObject *>( PyArray_GETCONTIGUOUS( pyArr ) ) );
213+
contiguousOwner = PyObjectPtr::own( reinterpret_cast<PyObject *>( PyArray_GETCONTIGUOUS( pyArr ) ) );
238214
contiguousArr = reinterpret_cast<PyArrayObject *>( contiguousOwner.get() );
239215
}
240216

@@ -244,9 +220,7 @@ class TemporalListWriter final : public csp::adapters::arrow::FieldWriter
244220
auto unit = datetimeUnitFromDescr( PyArray_DESCR( pyArr ) );
245221
int64_t scaling = scalingFromNumpyDtUnit( unit );
246222

247-
ARROW_OK_OR_THROW_WRITER(
248-
m_valueBuilder -> Reserve( static_cast<int64_t>( len ) ),
249-
"Failed to reserve temporal value builder" );
223+
ARROW_OK_OR_THROW_WRITER( m_valueBuilder -> Reserve( static_cast<int64_t>( len ) ), "Failed to reserve temporal value builder" );
250224
for( npy_intp i = 0; i < len; ++i )
251225
{
252226
if( data[i] == NPY_DATETIME_NAT )
@@ -258,37 +232,19 @@ class TemporalListWriter final : public csp::adapters::arrow::FieldWriter
258232

259233
private:
260234
std::shared_ptr<ArrowBuilderT> m_valueBuilder;
261-
std::shared_ptr<::arrow::ListBuilder> m_listBuilder;
262235
};
263236

264237
// Date writer: numpy datetime64 → Arrow List<Date32> (nanoseconds → days since epoch)
265-
class DateListWriter final : public csp::adapters::arrow::FieldWriter
238+
class DateListWriter final : public ListFieldWriterBase
266239
{
267240
public:
268241
DateListWriter( const std::string & columnName, const StructFieldPtr & structField )
269-
: FieldWriter( { columnName }, { ::arrow::list( ::arrow::date32() ) }, structField )
242+
: ListFieldWriterBase( { columnName }, { ::arrow::list( ::arrow::date32() ) }, structField )
270243
{
271244
m_valueBuilder = std::make_shared<::arrow::Date32Builder>();
272245
m_listBuilder = std::make_shared<::arrow::ListBuilder>( ::arrow::default_memory_pool(), m_valueBuilder );
273246
}
274247

275-
void reserve( int64_t numRows ) override
276-
{
277-
ARROW_OK_OR_THROW_WRITER( m_listBuilder -> Reserve( numRows ), "Failed to reserve date list builder" );
278-
}
279-
280-
void writeNull() override
281-
{
282-
ARROW_OK_OR_THROW_WRITER( m_listBuilder -> AppendNull(), "Failed to append null date list" );
283-
}
284-
285-
std::vector<std::shared_ptr<::arrow::Array>> finish() override
286-
{
287-
std::shared_ptr<::arrow::Array> arr;
288-
ARROW_OK_OR_THROW_WRITER( m_listBuilder -> Finish( &arr ), "Failed to finish date list array" );
289-
return { arr };
290-
}
291-
292248
protected:
293249
void doWrite( const Struct * s ) override
294250
{
@@ -302,8 +258,7 @@ class DateListWriter final : public csp::adapters::arrow::FieldWriter
302258
PyObjectPtr contiguousOwner;
303259
if( !PyArray_IS_C_CONTIGUOUS( pyArr ) )
304260
{
305-
contiguousOwner = PyObjectPtr::own(
306-
reinterpret_cast<PyObject *>( PyArray_GETCONTIGUOUS( pyArr ) ) );
261+
contiguousOwner = PyObjectPtr::own( reinterpret_cast<PyObject *>( PyArray_GETCONTIGUOUS( pyArr ) ) );
307262
contiguousArr = reinterpret_cast<PyArrayObject *>( contiguousOwner.get() );
308263
}
309264

@@ -313,9 +268,7 @@ class DateListWriter final : public csp::adapters::arrow::FieldWriter
313268
auto unit = datetimeUnitFromDescr( PyArray_DESCR( pyArr ) );
314269
int64_t scaling = scalingFromNumpyDtUnit( unit );
315270

316-
ARROW_OK_OR_THROW_WRITER(
317-
m_valueBuilder -> Reserve( static_cast<int64_t>( len ) ),
318-
"Failed to reserve date value builder" );
271+
ARROW_OK_OR_THROW_WRITER( m_valueBuilder -> Reserve( static_cast<int64_t>( len ) ), "Failed to reserve date value builder" );
319272
for( npy_intp i = 0; i < len; ++i )
320273
{
321274
if( data[i] == NPY_DATETIME_NAT )
@@ -327,7 +280,6 @@ class DateListWriter final : public csp::adapters::arrow::FieldWriter
327280

328281
private:
329282
std::shared_ptr<::arrow::Date32Builder> m_valueBuilder;
330-
std::shared_ptr<::arrow::ListBuilder> m_listBuilder;
331283
};
332284

333285
// --- Dispatch by npy_type ---
@@ -409,21 +361,30 @@ class NumpyNDArrayWriter final : public csp::adapters::arrow::FieldWriter
409361
// We call writeNext on the inner data writer which will check isSet and delegate to its doWrite
410362
m_dataWriter -> writeNext( s );
411363

412-
// Write shape/dims
364+
// Write shape/dims — use null when shape is unchanged from previous row
413365
auto & dgt = m_field -> value<DialectGenericType>( s );
414366
auto shape = m_shapeCallback( dgt );
415367

416-
ARROW_OK_OR_THROW_WRITER( m_dimsListBuilder -> Append(), "Failed to start dims list" );
417-
ARROW_OK_OR_THROW_WRITER(
418-
m_dimsValueBuilder -> AppendValues( shape.data(), static_cast<int64_t>( shape.size() ) ),
419-
"Failed to append dim values" );
368+
if( shape == m_lastShape )
369+
{
370+
ARROW_OK_OR_THROW_WRITER( m_dimsListBuilder -> AppendNull(), "Failed to append null dims" );
371+
}
372+
else
373+
{
374+
ARROW_OK_OR_THROW_WRITER( m_dimsListBuilder -> Append(), "Failed to start dims list" );
375+
ARROW_OK_OR_THROW_WRITER(
376+
m_dimsValueBuilder -> AppendValues( shape.data(), static_cast<int64_t>( shape.size() ) ),
377+
"Failed to append dim values" );
378+
m_lastShape = std::move( shape );
379+
}
420380
}
421381

422382
private:
423383
ShapeCallback m_shapeCallback;
424384
std::unique_ptr<csp::adapters::arrow::FieldWriter> m_dataWriter;
425385
std::shared_ptr<::arrow::Int64Builder> m_dimsValueBuilder;
426386
std::shared_ptr<::arrow::ListBuilder> m_dimsListBuilder;
387+
std::vector<int64_t> m_lastShape;
427388
};
428389

429390
#undef ARROW_OK_OR_THROW_WRITER
@@ -432,6 +393,9 @@ class NumpyNDArrayWriter final : public csp::adapters::arrow::FieldWriter
432393
// These write all elements of a numpy array into an arrow value builder
433394
// (list start/end is handled by the caller, e.g. ListColumnArrayBuilder).
434395

396+
template<typename T> struct ArrowArrayType { using type = T; };
397+
template<> struct ArrowArrayType<bool> { using type = uint8_t; };
398+
435399
template<typename CspT, typename ArrowBuilderT>
436400
inline csp::adapters::arrow::ListItemsWriter makeNativeListItemsWriter(
437401
std::shared_ptr<ArrowBuilderT> valueBuilder )
@@ -443,48 +407,24 @@ inline csp::adapters::arrow::ListItemsWriter makeNativeListItemsWriter(
443407

444408
if( PyArray_IS_C_CONTIGUOUS( pyArr ) )
445409
{
446-
auto * data = reinterpret_cast<const typename ArrowBuilderT::value_type *>( PyArray_DATA( pyArr ) );
410+
using CastT = typename ArrowArrayType<typename ArrowBuilderT::value_type>::type;
411+
auto * data = reinterpret_cast<const CastT *>( PyArray_DATA( pyArr ) );
447412
auto s = valueBuilder -> AppendValues( data, static_cast<int64_t>( len ) );
448413
CSP_TRUE_OR_THROW_RUNTIME( s.ok(), "Failed to append list elements: " << s.ToString() );
449414
}
450415
else
451416
{
417+
using CastT = typename ArrowArrayType<typename ArrowBuilderT::value_type>::type;
452418
PyObjectPtr contiguousOwner{ PyObjectPtr::own(
453419
reinterpret_cast<PyObject *>( PyArray_GETCONTIGUOUS( pyArr ) ) ) };
454-
auto * data = reinterpret_cast<const typename ArrowBuilderT::value_type *>(
420+
auto * data = reinterpret_cast<const CastT *>(
455421
PyArray_DATA( reinterpret_cast<PyArrayObject *>( contiguousOwner.get() ) ) );
456422
auto s = valueBuilder -> AppendValues( data, static_cast<int64_t>( len ) );
457423
CSP_TRUE_OR_THROW_RUNTIME( s.ok(), "Failed to append list elements: " << s.ToString() );
458424
}
459425
};
460426
}
461427

462-
inline csp::adapters::arrow::ListItemsWriter makeBoolListItemsWriter(
463-
std::shared_ptr<::arrow::BooleanBuilder> valueBuilder )
464-
{
465-
return [valueBuilder]( const DialectGenericType & dgt )
466-
{
467-
auto * pyArr = reinterpret_cast<PyArrayObject *>( csp::python::toPythonBorrowed( dgt ) );
468-
npy_intp len = PyArray_SIZE( pyArr );
469-
470-
if( PyArray_IS_C_CONTIGUOUS( pyArr ) )
471-
{
472-
auto * data = reinterpret_cast<const uint8_t *>( PyArray_DATA( pyArr ) );
473-
auto s = valueBuilder -> AppendValues( data, static_cast<int64_t>( len ) );
474-
CSP_TRUE_OR_THROW_RUNTIME( s.ok(), "Failed to append bool list elements: " << s.ToString() );
475-
}
476-
else
477-
{
478-
PyObjectPtr contiguousOwner{ PyObjectPtr::own(
479-
reinterpret_cast<PyObject *>( PyArray_GETCONTIGUOUS( pyArr ) ) ) };
480-
auto * data = reinterpret_cast<const uint8_t *>(
481-
PyArray_DATA( reinterpret_cast<PyArrayObject *>( contiguousOwner.get() ) ) );
482-
auto s = valueBuilder -> AppendValues( data, static_cast<int64_t>( len ) );
483-
CSP_TRUE_OR_THROW_RUNTIME( s.ok(), "Failed to append bool list elements: " << s.ToString() );
484-
}
485-
};
486-
}
487-
488428
inline csp::adapters::arrow::ListItemsWriter makeStringListItemsWriter( std::shared_ptr<::arrow::StringBuilder> valueBuilder )
489429
{
490430
return [valueBuilder]( const DialectGenericType & dgt )
@@ -568,7 +508,7 @@ inline void registerNumpyListFieldWriterFactory()
568508
case CspType::Type::BOOL:
569509
{
570510
auto b = std::make_shared<::arrow::BooleanBuilder>();
571-
return { b, numpy::makeBoolListItemsWriter( b ) };
511+
return { b, numpy::makeNativeListItemsWriter<bool, ::arrow::BooleanBuilder>( b ) };
572512
}
573513
case CspType::Type::STRING:
574514
{

0 commit comments

Comments
 (0)