Skip to content

[Variant] remove BorrowedShreddingState - #9791

Merged
alamb merged 6 commits into
apache:mainfrom
sdf-jkl:remove-borrowedshreddingstate
May 22, 2026
Merged

[Variant] remove BorrowedShreddingState#9791
alamb merged 6 commits into
apache:mainfrom
sdf-jkl:remove-borrowedshreddingstate

Conversation

@sdf-jkl

@sdf-jkl sdf-jkl commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Rationale for this change

Check issue

What changes are included in this PR?

  • Drop BorrowedShreddingState
  • Replace it with ShreddingState
  • Removed the lifetimes in unshred_variant as they required helpers to cover recursive ShreddingState handling.
  • Lifetimes removal introduces clone on NullBuffer. Extra 3 usize (24 bytes) per Array. Only used in NullUnshredVariantBuilder Removed the only place where NullBuffer was stored. No regression.

Are these changes tested?

Yes, unit tests.

Are there any user-facing changes?

No.

@github-actions github-actions Bot added the parquet-variant parquet-variant* crates label Apr 22, 2026
@sdf-jkl

sdf-jkl commented Apr 22, 2026

Copy link
Copy Markdown
Contributor Author

@alamb @scovich please take a look!

@sdf-jkl

sdf-jkl commented Apr 22, 2026

Copy link
Copy Markdown
Contributor Author

I also removed NullBuffer from NullUnshredVariantBuilder, since the arm where nulls are used is already covered by upper unshred_variant in loc96-97.

@scovich scovich 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.

Maybe I don't understand the change, but it seems like it introduces a lot of cloning of concrete array types? Not sure why that's needed when the [Borrowed]ShreddingState is only concerned about ArrayRef vs. &ArrayRef?

mod variant_get;
mod variant_to_arrow;

pub use variant_array::{BorrowedShreddingState, ShreddingState, VariantArray, VariantType};

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.

aside: Was this already an unused import? I wonder why clippy didn't flag it when it became unused?

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.

bump?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

pub use makes it visible to all outside crates and clippy can't know if someone else uses it.

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.

so this is technically a breaking API change

Self::$enum_variant(UnshredPrimitiveRowBuilder::new(
value,
typed_value.$cast_fn(),
typed_value.$cast_fn().clone(),

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.

Not sure I understand -- why would a cast return a borrowed value that needs cloning?

Self::Decimal32(DecimalUnshredRowBuilder::new(value, typed_value, *s as _))
Self::Decimal32(DecimalUnshredRowBuilder::new(
value,
typed_value.as_primitive().clone(),

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.

How expensive is it to clone a PrimitiveArray? It's not directly an Arc?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We're cloning a &PrimitiveArray<T>

pub struct PrimitiveArray<T: ArrowPrimitiveType> {
    data_type: DataType, // 24 bytes 
    /// Values data
    values: ScalarBuffer<T::Native>, // 24 bytes
    nulls: Option<NullBuffer>, // 48 bytes if Some
}

DataType::List(_) => Self::List(ListUnshredVariantBuilder::try_new(
value,
typed_value.as_list(),
typed_value.as_list::<i32>().clone(),

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.

Similar question for these -- cost of cloning struct and list arrays?

Comment on lines +397 to +410
struct UnshredPrimitiveRowBuilder<'a, T> {
value: Option<&'a ArrayRef>,
typed_value: &'a T,
struct UnshredPrimitiveRowBuilder<T> {
value: Option<ArrayRef>,
typed_value: T,

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.

Changes like this one seem unrelated to shared shredding state that needed an Option<&ArrayRef>? Even if value needs to be cloned, we could still keep a borrowed reference to typed_value which is anyway a bare type?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Can't keep the reference without using lifetimes in builders. It seems like removing them was a bad idea. I'll switch it back.

/// Creates a new UnshredVariantRowBuilder from the `(value, typed_value)` pair of a shredded
/// variant struct. Returns None for the None/None case - caller decides how to handle based on
/// context.
fn try_new_opt(inner_struct: &'a StructArray) -> Result<Option<Self>> {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is the place where BorrowedShreddingState was so useful.

For nested builders (List/Struct) we used to recursively call try_new_opt(field_array.try_into()?): the try_into produced a BorrowedShreddingState<'a> whose ArrayRefs were borrowed from the source field_array. The wrapper got consumed, but the &'a ArrayRefs inside it carried 'a along and could be stored in the builder.

In ShreddingState the ArrayRefs are owned, so once we call the function - the ShreddingState goes out of scope and the references point to free memory.

A workaround is using &StructArray since it's a reference to the outer Array 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.

Hmm. I wonder if we're going about this wrong.

First question: What problem are we actually trying to solve by eliminating BorrowedShreddingState? Is it just annoying to have two similar types? Or something else more serious?

Second question: What if (thought experiment) we standardized on BorrowedShreddingState everywhere instead?

  • Only use ShreddingState as an internal helper member of VariantArray, ShreddedVariantFieldArray, etc? (its job is to centralize the name-based lookup and validation code; we should probably push inner inside as well, since that's always there)
  • VariantArray::shredding_state() then returns self.shredding_state.borrow() (BorrowedShreddingState<'_> return type)
  • All functions that currently expect ShreddingState change to expect BorrowedShreddingState instead (I think this is already the case)

Third question: Where are we actually cloning StructArray, PrimitiveArray, etc today? Does the proposed change improve the situation, make it worse, or leave it unchanged? For example, VariantArray and ShreddedVariantFieldArray constructors both clone their input struct array today, and I don't think the current PR changes that.

Fourth question: Now that we know VariantArray is only a temporary helper that cannot actually impl Array, should we revisit the decision to make it an owned type? If VariantArray maintained references internally instead of owned values, then we could just use borrowed types everywhere and be done with it. Would the benefits be worth the headaches it causes code that uses VariantArray?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

  1. Nothing serious. I thought the BorrowedShreddingState redundant and wanted to remove it as dead code.
  2. We tried this in Support Shredded Lists/Array in variant_get #8354. There we follow the variant_get path and extract the shredding state. For Struct arrays we can use BorrowedShreddingState, but for List arrays, we write a new array using take kernel. We can't use BorrowedShreddingState here as reference outlives the value.
  3. PrimitiveArray is only cloned in ShreddingState and unshred_variant builder. StructArray is cloned in: VariantArray::try_new, ShreddedVariantFieldArray::try_new and canonicalize_shredded_types. We make the situation a little worse for ShreddingState by introducing one extra ArrayRef Arc bump per present field. Same for PrimitiveArrays for unshred builders.
  4. I think a good alternative is Arc'ing the inner StructArray.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@scovich Upon further reflection, the only performance diff, that this PR introduces is Arc bumps per Some(ArrayRef) ShreddingState depending on how it's shredded(perfectly - 1 or not -2) per variant_get path step.

The perf difference should be very minimal.

(I can still try to Cow ShreddingState to keep the Arc bumps to a minimum on the paths where we can use lifetimes)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No arc bumps on the variant_get Field path 😪

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.

Sorry I was too slow -- I agree with your assessment (from five hours ago) that 1-2 arc bumps per path step should be acceptable. But it looks like you already implemented the Cow version (two hours ago)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No worries, I was just playing around with the idea. We can always revert the commit and proceed with the previous version. I too think that it's not worth the added complexity.

@scovich scovich 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.

Looking at the latest Cow change, I'm not convinced the extra complexity is worth the tiny performance benefit it might bring (avoiding 1-2 arc clones per path step). The lifetimes are just too messy (the unreachable! line in into_borrowed_fields for owned shredding state is a big red flag, and a panic footgun).

Comment on lines 911 to 916
let value = if let Some(value_col) = inner_struct.column_by_name("value") {
validate_binary_array(value_col.as_ref(), "value")?;
Some(value_col)
Some(Cow::Borrowed(value_col))
} else {
None
};

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.

aside: isn't this just a transpose?

let value = inner_struct
    .column_by_name("value")
    .map(|value| {
        validate_binary_array(...)?;
        Ok(Cow::Borrowed(value))
    }
    .transpose()?; 

(tho it's not shorter, so meh)

// Cache the (value, typed_value) lookup. try_from validates the value column type.
let shredding_state = ShreddingState::try_from(inner_struct)?.into_static();

// Note this clone is cheap, it just bumps the ref count

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.

aside: inner_struct is a StructArray, not an ArrayRef, so clone has to round trip through ArrayData, no?

Comment on lines +40 to +43
/// The `typed_value` column the step descended through. Returned so the caller can union
/// its nulls into the accumulated null buffer without needing to re-borrow from the
/// (now-consumed) input state.
consumed_typed_value: Cow<'a, ArrayRef>,

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.

We can't just return a reference to the null buffer because of lifetime issues, I guess?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We'd clone otherwise, which is just an Arc Bump

return Ok(None);
};
let nested_struct = field.as_struct_opt().ok_or_else(|| {
ArrowError::InvalidArgumentError(format!(

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.

This would mean the Array doesn't follow the shredding spec? Because the field should be a struct with value/typed_value children?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes

Comment on lines +122 to +124
// `typed_value_arc` is the owned input and is about to move into the returned
// `consumed_typed_value`. We cannot also borrow into it (self-referential), so we
// clone `nested`'s columns to produce a `ShreddingState<'static>` (coerces to 'a).

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 don't understand how this could work? 'a is the lifetime of the root, and some cloned Arc will have a "local" lifetime instead. How can that owned value coerce to 'a lifetime? Or is 'a actually something shorter-lived than the root?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The ShreddingState::new returns a ShreddingState<'static> and it auto coerces into a <'a>. The values we pass in it are owned so it satisfies the <'a>.

/// Creates a new UnshredVariantRowBuilder from the `(value, typed_value)` pair of a shredded
/// variant struct. Returns None for the None/None case - caller decides how to handle based on
/// context.
fn try_new_opt(inner_struct: &'a StructArray) -> Result<Option<Self>> {

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.

Sorry I was too slow -- I agree with your assessment (from five hours ago) that 1-2 arc bumps per path step should be acceptable. But it looks like you already implemented the Cow version (two hours ago)?

@sdf-jkl sdf-jkl left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'll revert the commit

Comment on lines +40 to +43
/// The `typed_value` column the step descended through. Returned so the caller can union
/// its nulls into the accumulated null buffer without needing to re-borrow from the
/// (now-consumed) input state.
consumed_typed_value: Cow<'a, ArrayRef>,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We'd clone otherwise, which is just an Arc Bump

return Ok(None);
};
let nested_struct = field.as_struct_opt().ok_or_else(|| {
ArrowError::InvalidArgumentError(format!(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes

Comment on lines +122 to +124
// `typed_value_arc` is the owned input and is about to move into the returned
// `consumed_typed_value`. We cannot also borrow into it (self-referential), so we
// clone `nested`'s columns to produce a `ShreddingState<'static>` (coerces to 'a).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The ShreddingState::new returns a ShreddingState<'static> and it auto coerces into a <'a>. The values we pass in it are owned so it satisfies the <'a>.

@sdf-jkl

sdf-jkl commented May 18, 2026

Copy link
Copy Markdown
Contributor Author

@scovich I've reverted the commit.

@scovich scovich 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.

Looks like a great simplification, now that cloning shredding state is just a 2x Arc bump instead of having to make an ArrayData round tripl

mod variant_get;
mod variant_to_arrow;

pub use variant_array::{BorrowedShreddingState, ShreddingState, VariantArray, VariantType};

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.

bump?

@scovich

scovich commented May 20, 2026

Copy link
Copy Markdown
Contributor

BTW do we have any benchmarks that could sanity check our belief that this change does not hurt performance?

@scovich
scovich requested a review from alamb May 20, 2026 16:29
@sdf-jkl

sdf-jkl commented May 20, 2026

Copy link
Copy Markdown
Contributor Author

BTW do we have any benchmarks that could sanity check our belief that this change does not hurt performance?

Not yet. We could make some for variant_get/shred_variant

@alamb

alamb commented May 20, 2026

Copy link
Copy Markdown
Contributor

run benchmarks variant_builder variant_kernels variant_validation

@alamb alamb added the api-change Changes to the arrow API label May 20, 2026
@adriangbot

Copy link
Copy Markdown

🤖 Arrow criterion benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c4501572052-237-p2mp4 6.12.68+ #1 SMP Wed Apr 1 02:23:28 UTC 2026 aarch64 GNU/Linux

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected

Comparing remove-borrowedshreddingstate (cde17de) to fd1c5b3 (merge-base) diff
BENCH_NAME=variant_validation
BENCH_COMMAND=cargo bench --features=arrow,async,test_common,experimental,object_store --bench variant_validation
BENCH_FILTER=
Results will be posted here when complete


File an issue against this benchmark runner

@alamb

alamb commented May 20, 2026

Copy link
Copy Markdown
Contributor

The high level idea seems reasonable to me -- I kicked off the benchmarks that we do have -- let's see if they tell us anything

@adriangbot

Copy link
Copy Markdown

🤖 Arrow criterion benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c4501572052-235-wp7gc 6.12.68+ #1 SMP Wed Apr 1 02:23:28 UTC 2026 aarch64 GNU/Linux

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected

Comparing remove-borrowedshreddingstate (cde17de) to fd1c5b3 (merge-base) diff
BENCH_NAME=variant_builder
BENCH_COMMAND=cargo bench --features=arrow,async,test_common,experimental,object_store --bench variant_builder
BENCH_FILTER=
Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Arrow criterion benchmark completed (GKE) | trigger

Instance: c4a-highmem-16 (12 vCPU / 65 GiB)

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected
Details

group                               main                                   remove-borrowedshreddingstate
-----                               ----                                   -----------------------------
bench_validate_complex_object       1.00    182.9±5.95µs        ? ?/sec    1.00    182.5±5.31µs        ? ?/sec
bench_validate_large_nested_list    1.00     19.3±0.48ms        ? ?/sec    1.00     19.3±0.49ms        ? ?/sec
bench_validate_large_object         1.00     52.4±1.28ms        ? ?/sec    1.00     52.4±1.27ms        ? ?/sec

Resource Usage

base (merge-base)

Metric Value
Wall time 30.0s
Peak memory 3.1 GiB
Avg memory 3.0 GiB
CPU user 29.1s
CPU sys 0.6s
Peak spill 0 B

branch

Metric Value
Wall time 35.0s
Peak memory 3.1 GiB
Avg memory 3.0 GiB
CPU user 31.5s
CPU sys 0.1s
Peak spill 0 B

File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Arrow criterion benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c4501572052-236-772h5 6.12.68+ #1 SMP Wed Apr 1 02:23:28 UTC 2026 aarch64 GNU/Linux

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected

Comparing remove-borrowedshreddingstate (cde17de) to fd1c5b3 (merge-base) diff
BENCH_NAME=variant_kernels
BENCH_COMMAND=cargo bench --features=arrow,async,test_common,experimental,object_store --bench variant_kernels
BENCH_FILTER=
Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Arrow criterion benchmark completed (GKE) | trigger

Instance: c4a-highmem-16 (12 vCPU / 65 GiB)

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected
Details

group                                       main                                   remove-borrowedshreddingstate
-----                                       ----                                   -----------------------------
bench_extend_metadata_builder               1.00     21.3±0.10ms        ? ?/sec    1.02     21.8±0.10ms        ? ?/sec
bench_object_field_names_reverse_order      1.11     13.5±0.36ms        ? ?/sec    1.00     12.2±0.13ms        ? ?/sec
bench_object_list_partially_same_schema     1.01  1065.2±13.04µs        ? ?/sec    1.00  1059.8±12.26µs        ? ?/sec
bench_object_list_same_schema               1.00     22.4±0.10ms        ? ?/sec    1.01     22.6±0.17ms        ? ?/sec
bench_object_list_unknown_schema            1.00     11.2±0.04ms        ? ?/sec    1.01     11.3±0.08ms        ? ?/sec
bench_object_partially_same_schema          1.00      2.7±0.01ms        ? ?/sec    1.01      2.7±0.02ms        ? ?/sec
bench_object_same_schema                    1.00     32.8±0.38ms        ? ?/sec    1.05     34.4±0.14ms        ? ?/sec
bench_object_unknown_schema                 1.00     13.5±0.02ms        ? ?/sec    1.01     13.6±0.17ms        ? ?/sec
iteration/unvalidated_fallible_iteration    1.00      2.0±0.00ms        ? ?/sec    1.00      2.0±0.00ms        ? ?/sec
iteration/validated_iteration               1.04     39.8±2.44µs        ? ?/sec    1.00     38.3±0.70µs        ? ?/sec
validation/unvalidated_construction         1.00      4.4±0.08µs        ? ?/sec    1.00      4.4±0.11µs        ? ?/sec
validation/validated_construction           1.00     63.7±0.56µs        ? ?/sec    1.01     64.2±0.50µs        ? ?/sec
validation/validation_cost                  1.00     56.5±0.46µs        ? ?/sec    1.00     56.6±0.55µs        ? ?/sec

Resource Usage

base (merge-base)

Metric Value
Wall time 140.0s
Peak memory 3.1 GiB
Avg memory 3.1 GiB
CPU user 133.9s
CPU sys 2.4s
Peak spill 0 B

branch

Metric Value
Wall time 140.0s
Peak memory 3.1 GiB
Avg memory 3.1 GiB
CPU user 133.9s
CPU sys 1.3s
Peak spill 0 B

File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Arrow criterion benchmark completed (GKE) | trigger

Instance: c4a-highmem-16 (12 vCPU / 65 GiB)

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected
Details

group                                                                                             main                                   remove-borrowedshreddingstate
-----                                                                                             ----                                   -----------------------------
batch_json_string_to_variant json_list 8k string                                                  1.05     23.0±0.25ms        ? ?/sec    1.00     21.9±0.29ms        ? ?/sec
batch_json_string_to_variant object - 1 depth(100 fields) random_json(2436 bytes per document)    1.01    154.7±0.66ms        ? ?/sec    1.00    153.9±0.48ms        ? ?/sec
batch_json_string_to_variant object - 1 depth(200 fields) random_json(4871 bytes per document)    1.01    326.9±1.05ms        ? ?/sec    1.00    324.9±0.86ms        ? ?/sec
batch_json_string_to_variant random_json(2633 bytes per document)                                 1.00    227.1±0.66ms        ? ?/sec    1.00    227.6±0.56ms        ? ?/sec
batch_json_string_to_variant repeated_struct 8k string                                            1.00      6.6±0.04ms        ? ?/sec    1.00      6.6±0.12ms        ? ?/sec
variant_get_primitive                                                                             1.00    622.3±6.80ns        ? ?/sec    1.01    626.2±3.15ns        ? ?/sec
variant_get_shredded_utf8                                                                         1.00    634.0±8.07ns        ? ?/sec    1.00    631.3±4.90ns        ? ?/sec

Resource Usage

base (merge-base)

Metric Value
Wall time 160.0s
Peak memory 3.5 GiB
Avg memory 3.3 GiB
CPU user 153.8s
CPU sys 3.9s
Peak spill 0 B

branch

Metric Value
Wall time 160.0s
Peak memory 3.5 GiB
Avg memory 3.3 GiB
CPU user 153.0s
CPU sys 3.0s
Peak spill 0 B

File an issue against this benchmark runner

mod variant_get;
mod variant_to_arrow;

pub use variant_array::{BorrowedShreddingState, ShreddingState, VariantArray, VariantType};

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.

so this is technically a breaking API change

@alamb
alamb merged commit df89537 into apache:main May 22, 2026
17 checks passed
@alamb

alamb commented May 22, 2026

Copy link
Copy Markdown
Contributor

Thanks @sdf-jkl and @scovich

@sdf-jkl
sdf-jkl deleted the remove-borrowedshreddingstate branch May 22, 2026 18:26
Rich-T-kid pushed a commit to Rich-T-kid/arrow-rs that referenced this pull request Jun 2, 2026
# Which issue does this PR close?

<!--
We generally require a GitHub issue to be filed for all bug fixes and
enhancements and this helps us generate change logs for our releases.
You can link an issue to this PR using the GitHub syntax.
-->

- Closes apache#9790.

# Rationale for this change

Check issue

<!--
Why are you proposing this change? If this is already explained clearly
in the issue then this section is not needed.
Explaining clearly why changes are proposed helps reviewers understand
your changes and offer better suggestions for fixes.
-->

# What changes are included in this PR?

- Drop `BorrowedShreddingState`
- Replace it with `ShreddingState`
- ~~Removed the lifetimes in `unshred_variant` as they required helpers
to cover recursive `ShreddingState` handling.~~
- ~~Lifetimes removal introduces clone on `NullBuffer`. Extra 3 usize
(24 bytes) per `Array`. Only used in `NullUnshredVariantBuilder`~~
Removed the only place where `NullBuffer` was stored. No regression.
<!--
There is no need to duplicate the description in the issue here but it
is sometimes worth providing a summary of the individual changes in this
PR.
-->

# Are these changes tested?

Yes, unit tests.
<!--
We typically require tests for all PRs in order to:
1. Prevent the code from being accidentally broken by subsequent changes
2. Serve as another way to document the expected behavior of the code

If tests are not included in your PR, please explain why (for example,
are they covered by existing tests)?
-->

# Are there any user-facing changes?

No.
<!--
If there are user-facing changes then we may require documentation to be
updated before approving the PR.

If there are any breaking changes to public APIs, please call them out.
-->
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 parquet-variant parquet-variant* crates

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Variant] Remove BorrowedShreddingState

4 participants