This is an automated email from the ASF dual-hosted git repository.

Jefffrey pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git


The following commit(s) were added to refs/heads/main by this push:
     new 1535dc4d38 perf(variant): build BinaryView arrays directly (#10640)
1535dc4d38 is described below

commit 1535dc4d3842d1d4eea74ffa975a3e11fea8e869
Author: cakeni <[email protected]>
AuthorDate: Sat Aug 22 15:12:22 2026 +0800

    perf(variant): build BinaryView arrays directly (#10640)
    
    # Which issue does this PR close?
    
    - Closes #10621.
    
    # Rationale for this change
    
    Finalizing VariantArrayBuilder replayed every offset through
    BinaryViewBuilder::try_append_view, repeating bounds and value
    validation for buffers and offsets produced internally by the Variant
    builders.
    
    # What changes are included in this PR?
    
    - Add a focused Criterion benchmark that times only build() for 262,144
    small values.
    - Construct views directly from the recorded offset slices.
    - Build the final BinaryViewArray from those validated views, with the
    safety invariants documented at the unchecked constructor.
    
    # Are these changes tested?
    
    - cargo +stable-x86_64-pc-windows-gnu test -p parquet-variant-compute
    --lib (347 passed)
    - cargo +stable-x86_64-pc-windows-gnu bench -p parquet-variant-compute
    --bench variant_kernels -- variant_array_builder_build_262k_small_values
    --noplot
      - Before: [2.9213 ms, 2.9390 ms, 2.9577 ms]
      - After: [2.0393 ms, 2.0538 ms, 2.0689 ms]
      - Criterion change: [-30.816%, -30.118%, -29.428%], p = 0.00
    - cargo fmt --all -- --check
    - git diff --check
    
    # Are there any user-facing changes?
    
    No API or behavior changes. VariantArrayBuilder::build is approximately
    30% faster in the focused many-small-values benchmark.
    
    ## AI assistance
    
    I identified and evaluated this optimization, chose the final approach,
    and reviewed and refined the implementation and benchmark. OpenAI Codex
    assisted with code exploration, drafting, and benchmark preparation.
    
    Co-authored-by: Jeffrey Vo <[email protected]>
---
 parquet-variant-compute/benches/variant_kernels.rs | 20 ++++++++++++++-
 .../src/variant_array_builder.rs                   | 29 +++++++++++-----------
 2 files changed, 34 insertions(+), 15 deletions(-)

diff --git a/parquet-variant-compute/benches/variant_kernels.rs 
b/parquet-variant-compute/benches/variant_kernels.rs
index 800633b571..7fec1ed786 100644
--- a/parquet-variant-compute/benches/variant_kernels.rs
+++ b/parquet-variant-compute/benches/variant_kernels.rs
@@ -18,7 +18,7 @@
 use arrow::array::{Array, ArrayRef, BinaryViewArray, BinaryViewBuilder, 
StringArray, StructArray};
 use arrow::buffer::Buffer;
 use arrow_schema::{DataType, Field, FieldRef, Fields};
-use criterion::{Criterion, criterion_group, criterion_main};
+use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
 use parquet_variant::{EMPTY_VARIANT_METADATA_BYTES, Variant, VariantBuilder, 
VariantPath};
 use parquet_variant_compute::{
     GetOptions, VariantArray, VariantArrayBuilder, json_to_variant, 
variant_get,
@@ -33,6 +33,23 @@ use std::fmt::Write;
 use std::sync::Arc;
 
 const VARIANT_GET_UNSHREDDED_OBJECT_ROWS: usize = 262_144;
+const VARIANT_ARRAY_BUILD_ROWS: usize = 262_144;
+
+fn variant_array_builder_build_bench(c: &mut Criterion) {
+    c.bench_function("variant_array_builder_build_262k_small_values", |b| {
+        b.iter_batched(
+            || {
+                let mut builder = 
VariantArrayBuilder::new(VARIANT_ARRAY_BUILD_ROWS);
+                for value in 0..VARIANT_ARRAY_BUILD_ROWS {
+                    builder.append_variant(Variant::Int8((value % 128) as i8));
+                }
+                builder
+            },
+            |builder| std::hint::black_box(builder.build()),
+            BatchSize::LargeInput,
+        )
+    });
+}
 
 fn benchmark_batch_json_string_to_variant(c: &mut Criterion) {
     let input_array = 
StringArray::from_iter_values(json_repeated_struct(8000));
@@ -189,6 +206,7 @@ criterion_group!(
     variant_get_bench,
     variant_get_shredded_utf8_bench,
     variant_get_unshredded_object_path_bench,
+    variant_array_builder_build_bench,
     benchmark_batch_json_string_to_variant
 );
 criterion_main!(benches);
diff --git a/parquet-variant-compute/src/variant_array_builder.rs 
b/parquet-variant-compute/src/variant_array_builder.rs
index 3dd9714ce2..d7bcd08937 100644
--- a/parquet-variant-compute/src/variant_array_builder.rs
+++ b/parquet-variant-compute/src/variant_array_builder.rs
@@ -18,7 +18,9 @@
 //! [`VariantArrayBuilder`] implementation
 
 use crate::VariantArray;
-use arrow::array::{ArrayRef, BinaryViewArray, BinaryViewBuilder, 
NullBufferBuilder, StructArray};
+use arrow::array::builder::make_view;
+use arrow::array::{ArrayRef, BinaryViewArray, NullBufferBuilder, StructArray};
+use arrow::buffer::Buffer;
 use arrow_schema::{ArrowError, DataType, Field, Fields};
 use parquet_variant::{
     BuilderSpecificState, ListBuilder, MetadataBuilder, ObjectBuilder, 
Variant, VariantBuilderExt,
@@ -458,22 +460,21 @@ impl VariantBuilderExt for 
VariantValueArrayBuilderExt<'_> {
 }
 
 fn binary_view_array_from_buffers(buffer: Vec<u8>, offsets: Vec<usize>) -> 
BinaryViewArray {
-    // All offsets are less than or equal to the buffer length, so we can 
safely cast all offsets
-    // inside the loop below, as long as the buffer length fits in u32.
-    u32::try_from(buffer.len()).expect("buffer length should fit in u32");
-
-    let mut builder = BinaryViewBuilder::with_capacity(offsets.len());
-    let block = builder.append_block(buffer.into());
-    // TODO this can be much faster if it creates the views directly during 
append
-    let mut start = 0;
+    // Each builder records the current buffer length after appending a row, 
so offsets are
+    // monotonically increasing and bounded by the final buffer length.
+    assert!(buffer.len() < u32::MAX as usize);
+
+    let buffer = Buffer::from(buffer);
+    let mut views = Vec::with_capacity(offsets.len());
+    let mut start = 0_usize;
     for end in offsets {
-        let end = end as u32; // Safe cast: validated max offset fits in u32 
above
-        builder
-            .try_append_view(block, start, end - start)
-            .expect("Failed to append view");
+        views.push(make_view(&buffer[start..end], 0, start as u32));
         start = end;
     }
-    builder.finish()
+
+    // SAFETY: `make_view` constructs every view from an in-bounds slice of 
buffer 0, and there
+    // are no nulls. The buffer length check above guarantees every offset 
fits in a `u32`.
+    unsafe { BinaryViewArray::new_unchecked(views.into(), vec![buffer], None) }
 }
 
 #[cfg(test)]

Reply via email to