Repository navigation
Conversation
…rray` for faster `slice`
Arc<[Buffer]> instead of raw Vec<Buffer> in `GenericByteViewA…Arc<[Buffer]> instead of raw Vec<Buffer> in GenericByteViewArray for faster slice
Arc<[Buffer]> instead of raw Vec<Buffer> in GenericByteViewArray for faster sliceArc<[Buffer]> instead of raw Vec<Buffer> in GenericByteViewArray for faster slice
|
Unfortunately the use of impl is still a breaking change as it could impact type inference, e.g if collecting an interator into the argument |
|
|
||
| /// Deconstruct this array into its constituent parts | ||
| pub fn into_parts(self) -> (ScalarBuffer<u128>, Vec<Buffer>, Option<NullBuffer>) { | ||
| pub fn into_parts(self) -> (ScalarBuffer<u128>, Arc<[Buffer]>, Option<NullBuffer>) { |
There was a problem hiding this comment.
This is also a breaking change
|
FWIW the reason "breaking change" is important is that it restricts when we can merge this PR: https://github.com/apache/arrow-rs/blob/master/CONTRIBUTING.md#breaking-changes |
| data_type: DataType, | ||
| views: ScalarBuffer<u128>, | ||
| buffers: Vec<Buffer>, | ||
| buffers: Arc<[Buffer]>, |
There was a problem hiding this comment.
What's the rationale for Arc<[Buffer]> vs Vec<Arc>?
There was a problem hiding this comment.
Cloning an Arc is relatively cheap (no allocation), cloning a Vec isn't.
There was a problem hiding this comment.
i get it. However, if i understand correctly, Arc<[Buffer]> means the buffers can be passed around and shared only when they are within single slice, which can be limiting. For example, Can i merge two arrays, combining their Arc<Buffer> s without moving or cloning the buffers?
There was a problem hiding this comment.
Can i merge two arrays, combining their Arc s without moving or cloning the buffers?
No -- you would have to create a new Vec<Buffer> (or some other way to get Arc<[Buffer]>)
So while there are some cases where new allocations are required, slicing / cloning is faster
There was a problem hiding this comment.
you would have to create a new
Vec<Buffer>
but that would prevent buffer sharing between two arrays, right?
slicing / cloning is faster
cloning yes
slicing -- i didn't see it
There was a problem hiding this comment.
I think the observation is that during StringViewArray::slice, the slice actually happens on the views -- the buffers (that the views can point at) must be copied
Here is the clone of buffers: https://docs.rs/arrow-array/53.1.0/src/arrow_array/array/byte_view_array.rs.html#385
|
Coming back to this now, I think we could merge this although I am curious if there is something funny going on here, which is why this is showing up in benchmarks. Someone popped up on discord the other day reporting a StringViewArray with ~10k buffers, this would suggest something is likely off somewhere. I wonder if kernels are blindly concatenating identical buffers together, instead of using something like Buffer::ptr_eq to avoid a new entry for the exact same buffer allocation? |
What was happening in DataFusion was we had a Filter --> Coalesce chain and thus basically calling However, to your point, it may well be the case that the input RecordBatches shared the same underlying buffer so maybe the same buffer was being appended multiple times @XiangpengHao do you remember if you looked for this ? (related to "Section 3.5: Buffer size tuning |
I think so: arrow-rs/arrow-data/src/transform/mod.rs Lines 630 to 637 in def94a8
I have experienced this when loading string view from Parquet. If the parquet data has 10k buffers of string data, the string view will just hold them. Typically we should run a filter and then gc it. This is handled in DF but if used out side DF users might need to do something similar like this: https://github.com/apache/datafusion/blob/c0ca4b4e449e07c3bcd6f3593fa31dd31ed5e0c5/datafusion/physical-plan/src/coalesce/mod.rs#L201-L221 In other words, if StringViewArray is constructed by us, it's very unlikely to have 10k buffers as we will exponentially grow the buffers ultil 2MB; so 10k buffers means ~20GB data |
Good call -- @onursatici has tracked down a related (perhaps the same) issue: |
|
All the discussion above not withstanding, I think the fact that this PR improves |
| data_type: T::DATA_TYPE, | ||
| views, | ||
| buffers, | ||
| buffers: buffers.into(), |
There was a problem hiding this comment.
I think we should take impl Into<Arc<[Buffer]>> in this method as well
Agreed What do people think about extracting a newtype |
I think it would be a great idea. @ShiKaiWi is that something you would be willing to try? |
Sorry for the late reply (I was a bit busy with work in the last few months). I'd love to try it on this weekend. |
Thank @ShiKaiWi -- I don't think there is any huge rush to get this feature in, so it would be great if you could do so ❤️ |
|
Converting to draft as I think there is some new planned work here. |
|
May I pick this up? It seems like it would be useful for DF :) |
Please do -- I agree I have seen this come up in some profiles The other thing Arc<[Buffer]> might help with is reduce the cost of |
|
Closing this PR as stale. Looks like it might be superseded by #9016 anyway. |
…aper cloning (#10708) # Which issue does this PR close? - Related to #10692. # Rationale for this change This change follows directly from the existing ByteView design history: - #6408 traced slow ByteView slicing to allocating and cloning the backing-buffer list. The discussion specifically identified `take` as another operation that would benefit from sharing this list. - #6427 distinguished two related but separate concerns: very large buffer lists may indicate missing GC or deduplication, but avoiding allocation when an operation retains the complete list is independently worthwhile. - #6808 demonstrated why eager deduplication in a general-purpose path is a different tradeoff: it added substantial overhead to common operations, and review favored treating ByteView compaction more holistically. - #9016 subsequently changed `GenericByteViewArray` to store the collection as `Arc<[Buffer]>`, making it possible to share the complete list without allocation. The downstream impact is concrete. [apache/datafusion#16206](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? ```shell 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.
…aper cloning (apache#10708) # Which issue does this PR close? - Related to apache#10692. # Rationale for this change This change follows directly from the existing ByteView design history: - apache#6408 traced slow ByteView slicing to allocating and cloning the backing-buffer list. The discussion specifically identified `take` as another operation that would benefit from sharing this list. - apache#6427 distinguished two related but separate concerns: very large buffer lists may indicate missing GC or deduplication, but avoiding allocation when an operation retains the complete list is independently worthwhile. - apache#6808 demonstrated why eager deduplication in a general-purpose path is a different tradeoff: it added substantial overhead to common operations, and review favored treating ByteView compaction more holistically. - apache#9016 subsequently changed `GenericByteViewArray` to store the collection as `Arc<[Buffer]>`, making it possible to share the complete list without allocation. The downstream impact is concrete. [apache/datafusion#16206](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 apache#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 apache#10692 remains separate. # Are these changes tested? ```shell 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.
…aper cloning (apache#10708) # Which issue does this PR close? - Related to apache#10692. # Rationale for this change This change follows directly from the existing ByteView design history: - apache#6408 traced slow ByteView slicing to allocating and cloning the backing-buffer list. The discussion specifically identified `take` as another operation that would benefit from sharing this list. - apache#6427 distinguished two related but separate concerns: very large buffer lists may indicate missing GC or deduplication, but avoiding allocation when an operation retains the complete list is independently worthwhile. - apache#6808 demonstrated why eager deduplication in a general-purpose path is a different tradeoff: it added substantial overhead to common operations, and review favored treating ByteView compaction more holistically. - apache#9016 subsequently changed `GenericByteViewArray` to store the collection as `Arc<[Buffer]>`, making it possible to share the complete list without allocation. The downstream impact is concrete. [apache/datafusion#16206](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 apache#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 apache#10692 remains separate. # Are these changes tested? ```shell 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.
…aper cloning (apache#10708) # Which issue does this PR close? - Related to apache#10692. # Rationale for this change This change follows directly from the existing ByteView design history: - apache#6408 traced slow ByteView slicing to allocating and cloning the backing-buffer list. The discussion specifically identified `take` as another operation that would benefit from sharing this list. - apache#6427 distinguished two related but separate concerns: very large buffer lists may indicate missing GC or deduplication, but avoiding allocation when an operation retains the complete list is independently worthwhile. - apache#6808 demonstrated why eager deduplication in a general-purpose path is a different tradeoff: it added substantial overhead to common operations, and review favored treating ByteView compaction more holistically. - apache#9016 subsequently changed `GenericByteViewArray` to store the collection as `Arc<[Buffer]>`, making it possible to share the complete list without allocation. The downstream impact is concrete. [apache/datafusion#16206](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 apache#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 apache#10692 remains separate. # Are these changes tested? ```shell 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.
…aper cloning (apache#10708) # Which issue does this PR close? - Related to apache#10692. # Rationale for this change This change follows directly from the existing ByteView design history: - apache#6408 traced slow ByteView slicing to allocating and cloning the backing-buffer list. The discussion specifically identified `take` as another operation that would benefit from sharing this list. - apache#6427 distinguished two related but separate concerns: very large buffer lists may indicate missing GC or deduplication, but avoiding allocation when an operation retains the complete list is independently worthwhile. - apache#6808 demonstrated why eager deduplication in a general-purpose path is a different tradeoff: it added substantial overhead to common operations, and review favored treating ByteView compaction more holistically. - apache#9016 subsequently changed `GenericByteViewArray` to store the collection as `Arc<[Buffer]>`, making it possible to share the complete list without allocation. The downstream impact is concrete. [apache/datafusion#16206](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 apache#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 apache#10692 remains separate. # Are these changes tested? ```shell 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.
…aper cloning (apache#10708) # Which issue does this PR close? - Related to apache#10692. # Rationale for this change This change follows directly from the existing ByteView design history: - apache#6408 traced slow ByteView slicing to allocating and cloning the backing-buffer list. The discussion specifically identified `take` as another operation that would benefit from sharing this list. - apache#6427 distinguished two related but separate concerns: very large buffer lists may indicate missing GC or deduplication, but avoiding allocation when an operation retains the complete list is independently worthwhile. - apache#6808 demonstrated why eager deduplication in a general-purpose path is a different tradeoff: it added substantial overhead to common operations, and review favored treating ByteView compaction more holistically. - apache#9016 subsequently changed `GenericByteViewArray` to store the collection as `Arc<[Buffer]>`, making it possible to share the complete list without allocation. The downstream impact is concrete. [apache/datafusion#16206](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 apache#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 apache#10692 remains separate. # Are these changes tested? ```shell 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.
…aper cloning (apache#10708) # Which issue does this PR close? - Related to apache#10692. # Rationale for this change This change follows directly from the existing ByteView design history: - apache#6408 traced slow ByteView slicing to allocating and cloning the backing-buffer list. The discussion specifically identified `take` as another operation that would benefit from sharing this list. - apache#6427 distinguished two related but separate concerns: very large buffer lists may indicate missing GC or deduplication, but avoiding allocation when an operation retains the complete list is independently worthwhile. - apache#6808 demonstrated why eager deduplication in a general-purpose path is a different tradeoff: it added substantial overhead to common operations, and review favored treating ByteView compaction more holistically. - apache#9016 subsequently changed `GenericByteViewArray` to store the collection as `Arc<[Buffer]>`, making it possible to share the complete list without allocation. The downstream impact is concrete. [apache/datafusion#16206](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 apache#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 apache#10692 remains separate. # Are these changes tested? ```shell 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.
Which issue does this PR close?
Close #6408
Rationale for this change
In the
GenericByteViewArray, thebuffersfield is a raw vector, leading to heap allocation when some methods are called, e.g.clone,slice. UsingArc<[Buffer]>instead of the rawVec<Buffer>can avoid such heap allocation.And the newly-add benchmark cases about
sliceshows the improvement:What changes are included in this PR?
Use
Arc<[Buffer]>instead of the rawVec<Buffer>as the type ofbuffersfield ofGenericByteViewArray.Are there any user-facing changes?
The signature of the method
GenericByteViewArray::new_uncheckedis changed from:to
However, any usage of this method before this PR should still work without any modification.