[TASK-87] Optimize copying in CPP bindings#330
[TASK-87] Optimize copying in CPP bindings#330fresh-borzoni wants to merge 4 commits intoapache:mainfrom
Conversation
|
@zhaohaidao @luoyuxia PTAL 🙏 |
|
@fresh-borzoni Thanks for the pr. Maybe you can share the performance improvement benchmark in here. |
There was a problem hiding this comment.
Pull request overview
This PR refactors the C++ bindings FFI layer to remove per-row value copying and instead use opaque Rust-backed types for writes and reads, enabling zero-copy scan reads and a more efficient lookup API.
Changes:
- Replace
FfiDatum/FfiGenericRow/FfiScanRecordscopying with opaque Rust types (GenericRowInner,ScanResultInner,LookupResultInner) exposed viacxx. - Add zero-copy read APIs in C++ (
RowView,ScanRecordsiterator) and replace lookup(found, out_row)withLookupResult. - Add
AppendArrowBatchto write ArrowRecordBatches via the Arrow C Data Interface.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| bindings/cpp/src/types.rs | Updates type-id mappings (adds Char/Binary) and introduces resolve_row_types / owned conversion helpers for the new opaque-row write/read paths. |
| bindings/cpp/src/table.cpp | Implements C++ wrappers for the new opaque types (GenericRow, RowView, ScanRecords, LookupResult) and updates writers/scanners to use them. |
| bindings/cpp/src/lib.rs | Replaces row/scan/lookup copy-based FFI structs with opaque Rust types and accessor methods; adds Arrow batch import for append. |
| bindings/cpp/src/ffi_converter.hpp | Removes old GenericRow/ScanRecords conversions and adds support for custom_properties in table descriptors/info. |
| bindings/cpp/include/fluss.hpp | Updates the public C++ API: write-only GenericRow, new RowView/ScanRecords/LookupResult, new ChangeType, and custom_properties. |
| bindings/cpp/examples/kv_example.cpp | Migrates lookup example to LookupResult and demonstrates name-based getters. |
| bindings/cpp/examples/example.cpp | Migrates scan iteration to the new ScanRecords API and adds an AppendArrowBatch example. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Addressed comments. |
leekeiabstraction
left a comment
There was a problem hiding this comment.
Thank you for the PR! Not used to C++ so a bit out of water here but left some comments.
zhaohaidao
left a comment
There was a problem hiding this comment.
@fresh-borzoni Thanks for your pr. left minor comments.
|
@leekeiabstraction @zhaohaidao |
5975580 to
9fd91b3
Compare
leekeiabstraction
left a comment
There was a problem hiding this comment.
Thank you for the revision! Left a couple of comments
|
@leekeiabstraction @zhaohaidao Ty for the review! Addressed comments. |
47ad6df to
0bb4679
Compare
zhaohaidao
left a comment
There was a problem hiding this comment.
@fresh-borzoni Thank you. Only left one comment.
| allowed: impl FnOnce(&fcore::metadata::DataType) -> bool, | ||
| ) -> Result<&'a fcore::metadata::DataType, String> { | ||
| let col = get_column(columns, field)?; | ||
| if row.is_null_at(field) { |
There was a problem hiding this comment.
Can u add field < row.get_field_count() to avoid panic paths in InternalRow::is_null_at / get_* when schema and row length diverge
Summary
closes #87
Replace value-copying FFI layer with opaque Rust-backed types for zero-copy reads and cheaper writes in CPP bindings.
Changes
Datum/FfiDatum/FfiGenericRow/FfiScanRecordcopying infrastructure with three opaque Rust types —GenericRowInner(writes),ScanResultInner(scan reads),LookupResultInner(lookup reads)RowViewclass borrows directly from Rust scan results;LookupResultclass replaces(bool found, GenericRow out)patternBenchmark Results
Environment: Apple M1 Max, 32 GB RAM, macOS 15.7.1, local fluss cluster
Workload: 1M rows per run, averaged over 100 runs
Two scan configurations:
Each scan polls all 1M records from the server, accumulates them in memory,
then reads every field. Reported time covers both phases (poll + field access).
Memory is measured as C++ heap delta (
malloc_zone_statisticssize_in_use)before and after accumulating all records.
Throughput
Memory (held during scan)
Memory figures reflect worst-case accumulation. In typical streaming usage memory stays flat, but per-batch overhead is still reduced since the intermediate FfiDatum copy layer is eliminated entirely.