From e13d227e4c90acf7c7a05dca597dad7f14faa741 Mon Sep 17 00:00:00 2001 From: cakeni <2150015994@qq.com> Date: Wed, 12 Aug 2026 00:01:32 +0800 Subject: [PATCH] perf(variant): build binary views directly --- .../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 8ecf5bf44e20..5993b2676c2e 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 bc2d7d370c4a..7f8ec9389618 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<'a> VariantBuilderExt for VariantValueArrayBuilderExt<'a> { } fn binary_view_array_from_buffers(buffer: Vec, offsets: Vec) -> 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)]