Skip to content

perf: avoid cloning ByteView buffer lists in take and filter - #10708

Open
YimingQiao wants to merge 3 commits into
apache:mainfrom
YimingQiao:perf/share-byte-view-buffer-list
Open

perf: avoid cloning ByteView buffer lists in take and filter#10708
YimingQiao wants to merge 3 commits into
apache:mainfrom
YimingQiao:perf/share-byte-view-buffer-list

Conversation

@YimingQiao

@YimingQiao YimingQiao commented Aug 16, 2026

Copy link
Copy Markdown

Which issue does this PR close?

Rationale for this change

This change follows directly from the existing ByteView design history:

The downstream impact is concrete. apache/datafusion#16206 describes hash joins where concatenating build-side batches produces ByteView payload columns with many backing buffers, and constructing join output repeatedly calls take. In that pattern, cloning and later dropping every buffer handle can become a significant part of execution time. Arrow issue #10692 provides a compact multi-stage take and BatchCoalescer reproduction of the same ownership-metadata amplification.

take_byte_view and filter_byte_view do not yet use this shared representation. They rebuild the collection with data_buffers().to_vec(), allocating a new collection and cloning every Buffer. This makes the ownership bookkeeping for selection O(number of backing buffers), despite retaining exactly the same complete buffer list.

Selection already copies the chosen views while leaving their payloads zero-copy. The cost of retaining the unchanged backing-buffer collection should therefore not grow with the number of entries in that collection. This PR completes that narrow part of the earlier design while leaving buffer canonicalization as a separate problem.

What changes are included in this PR?

  • Change GenericByteViewArray::data_buffers() to return &Arc<[Buffer]>, allowing callers to inspect the buffers as before or clone the collection's Arc in O(1).
  • Change GenericByteViewArray::new_unchecked to accept Arc<[Buffer]> directly, making shared versus newly constructed buffer-list ownership explicit at each call site.
  • Use Arc::clone in the ByteView take and filter kernels instead of rebuilding the collection.
  • Replace three additional data_buffers().to_vec() call sites found by the stricter constructor signature with shared Arc clones.
  • Verify for both StringView and BinaryView that selection results share the input buffer collection.

This PR only removes repeated ownership-metadata cloning. It retains the same complete collection of backing buffers as before. It does not prune or deduplicate buffer entries, remap views, run GC, or copy string/binary payloads. The broader buffer-fragmentation problem described in #10692 remains separate.

Are these changes tested?

cargo fmt --all -- --check
cargo check --workspace --all-targets
cargo test -p arrow-array -p arrow-select -p arrow-row -p arrow-ipc
cargo test -p parquet --test arrow_reader invalid_utf8
cargo clippy -p arrow-array -p arrow-select -p arrow-row -p arrow-ipc -p parquet --all-targets --all-features -- -D warnings
cargo doc -p arrow-array --no-deps

A temporary local Criterion benchmark was used to validate the asymptotic behavior. It takes 8,192 views distributed across a varying number of buffer entries. The entries share one immutable payload allocation, isolating collection-ownership cost from payload size. main and this PR were built in separate Cargo target directories on an Intel Xeon Platinum 8474C:

Buffer entries main This PR Speedup
1 5.4900 us 5.4619 us 1.01x
16 5.7342 us 5.4577 us 1.05x
256 9.2806 us 5.4518 us 1.70x
4,096 65.774 us 5.4584 us 12.05x

Existing normal-size benchmarks did not regress:

Benchmark main This PR
take stringview 512 504.73 ns 439.40 ns
take stringview 1024 872.36 ns 773.48 ns
filter context mixed string view (kept 1/2) 73.406 us 72.083 us

Are there any user-facing changes?

There are two public signature changes. data_buffers() now returns &Arc<[Buffer]> instead of &[Buffer]; slice methods remain available through deref, while direct for iteration can use .iter(). new_unchecked now requires Arc<[Buffer]>; callers constructing a Vec<Buffer> can convert it with .into(). ByteView selection results now share the immutable backing-buffer collection instead of allocating an equivalent collection. Logical values, null handling, buffer indexes, payload lifetimes, and GC behavior are unchanged.

AI assistance

I used OpenAI Codex to help inspect the relevant implementation history, draft the patch and tests, and prepare the benchmark harness and PR text. I reviewed the implementation and benchmark methodology and ran the checks above locally.

@github-actions github-actions Bot added arrow Changes to the arrow crate arrow-select arrow-array labels Aug 16, 2026
@YimingQiao
YimingQiao marked this pull request as draft August 16, 2026 16:00
@YimingQiao
YimingQiao force-pushed the perf/share-byte-view-buffer-list branch from c884697 to 46c989e Compare August 16, 2026 16:01
@YimingQiao YimingQiao changed the title perf: share ByteView buffer lists in take and filter perf: avoid cloning ByteView buffer lists in take and filter Aug 16, 2026
@YimingQiao
YimingQiao marked this pull request as ready for review August 16, 2026 16:02
Comment thread arrow-array/src/array/byte_view_array.rs Outdated
Comment thread arrow/benches/take_kernels.rs Outdated

@alamb alamb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank you @YimingQiao and @Jefffrey -- this one looks good to me though I also have another potential idea (that would be more invasive)

&self.buffers
}

/// Returns a cloned `Arc` of the buffers storing non-inline string or binary data.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I wonder if we should just change the other APIs be consistent -- like make data_buffers return &Arc<[Buffer]> and change new_unchecked to take Arc<[Buffer]> directly (rather than Into<Arc<[Buffer]>> )

That way we could find more places that unecessairly copy these buffers 🤔

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

That worked nicely. I changed data_buffers() to return &Arc<[Buffer]> and made new_unchecked take Arc<[Buffer]> directly. Letting the compiler drive the update found three more .to_vec() call sites in the FFI, IPC, and Parquet tests; those now use Arc::clone, while callers that genuinely construct new buffer lists convert them explicitly. The workspace checks, relevant tests, clippy, and rustdoc all pass locally. This changes two public signatures, and I do not have permission to add labels here, so could you add api-change if appropriate?

@alamb alamb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank you @YimingQiao

@Jefffrey would you like to review this PR again before merging?

/// [`data_buffers`]: Self::data_buffers
/// The returned `Arc` can be cloned to share the buffers with another array without
/// allocating a new collection or cloning the individual buffers. To consume this
/// array and take ownership of its buffers, use [`Self::into_parts`].

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

👍

Comment thread arrow-array/src/ffi.rs
fn test_utf8_view_ffi_from_dangling_pointer() {
let empty = GenericByteViewBuilder::<StringViewType>::new().finish();
let buffers = empty.data_buffers().to_vec();
let buffers = Arc::clone(empty.data_buffers());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

it is only a test, but this is a nice improvement

@alamb alamb added the api-change Changes to the arrow API label Aug 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api-change Changes to the arrow API arrow Changes to the arrow crate arrow-array arrow-select performance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants