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

alamb 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 a173f012fa Improve performance of `concat_elements` ByteViewArray 
concatenation (#10161)
a173f012fa is described below

commit a173f012fa278ef5118c9c6c13b6ccb90c621f61
Author: Pepijn Van Eeckhoudt <[email protected]>
AuthorDate: Wed Jun 24 20:44:36 2026 +0200

    Improve performance of `concat_elements` ByteViewArray concatenation 
(#10161)
    
    # Which issue does this PR close?
    
    None
    
    # Rationale for this change
    
    During profiling of a DataFusion query String concatenation, in
    particular of two StringView arrays, proved to be a hotspot.
    This MR proposes a revised version of
    `concat_elements_string_view_array` which eliminates some overhead that
    comes from using a fairly generic implementation strategy.
    Benchmarking shows improvement of 20-40%.
    
    # What changes are included in this PR?
    
    - Replace `StringViewBuilder` based concatenation implementation with
    one that directly writes the various buffers of the array
    - Add checks in `NullBuffer::union` and `NullBuffer::union_many` to
    treat buffers with `null_count == 0` as equivalent to `None` so that
    `None` can be returned in more cases.
    
    # Are these changes tested?
    
    - Covered by existing tests, and some additional test cases added to
    ensure newly added code is covered
    - Added additional test cases to cover the changes to
    `NullBuffer::union` and `NullBuffer::union_many`
    
    # Are there any user-facing changes?
    
    No
    
    ---------
    
    Co-authored-by: Andrew Lamb <[email protected]>
---
 arrow-buffer/src/buffer/null.rs       |  47 ++++-
 arrow-string/src/concat_elements.rs   | 380 +++++++++++++++++++++++++++-------
 arrow/Cargo.toml                      |   5 +
 arrow/benches/concatenate_elements.rs |  92 ++++++++
 4 files changed, 448 insertions(+), 76 deletions(-)

diff --git a/arrow-buffer/src/buffer/null.rs b/arrow-buffer/src/buffer/null.rs
index 729beaa061..df8c73eea0 100644
--- a/arrow-buffer/src/buffer/null.rs
+++ b/arrow-buffer/src/buffer/null.rs
@@ -78,9 +78,11 @@ impl NullBuffer {
     /// can yield significant performance improvements over an iterator 
approach
     pub fn union(lhs: Option<&NullBuffer>, rhs: Option<&NullBuffer>) -> 
Option<NullBuffer> {
         match (lhs, rhs) {
-            (Some(lhs), Some(rhs)) => Some(Self::new(lhs.inner() & 
rhs.inner())),
-            (Some(n), None) | (None, Some(n)) => Some(n.clone()),
-            (None, None) => None,
+            (Some(lhs), Some(rhs)) if lhs.null_count() > 0 || rhs.null_count() 
> 0 => {
+                Some(Self::new(lhs.inner() & rhs.inner()))
+            }
+            (Some(n), None) | (None, Some(n)) if n.null_count() > 0 => 
Some(n.clone()),
+            (_, _) => None,
         }
     }
 
@@ -91,7 +93,10 @@ impl NullBuffer {
         nulls: impl IntoIterator<Item = Option<&'a NullBuffer>>,
     ) -> Option<NullBuffer> {
         // Unwrap to BooleanBuffer because BitAndAssign is not implemented for 
NullBuffer
-        let mut buffers = nulls.into_iter().filter_map(|nb| 
nb.map(NullBuffer::inner));
+        let mut buffers = nulls.into_iter().filter_map(|nb| match nb {
+            Some(nb) if nb.null_count > 0 => Some(nb.inner()),
+            _ => None,
+        });
         let first = buffers.next()?;
         let mut result = first.clone();
         for buf in buffers {
@@ -399,4 +404,38 @@ mod tests {
         let result = NullBuffer::union_many([] as [Option<&NullBuffer>; 0]);
         assert!(result.is_none());
     }
+
+    #[test]
+    fn test_union_many_no_nulls() {
+        let a = NullBuffer::from(&[true, true, true, true]);
+
+        let result = NullBuffer::union_many([Some(&a), Some(&a), Some(&a)]);
+        assert_eq!(result, None);
+    }
+
+    #[test]
+    fn test_union_no_nulls() {
+        let a = NullBuffer::from(&[true, true, true, true]);
+
+        let result = NullBuffer::union(Some(&a), Some(&a));
+        assert_eq!(result, None);
+
+        let result = NullBuffer::union(Some(&a), None);
+        assert_eq!(result, None);
+
+        let result = NullBuffer::union(None, Some(&a));
+        assert_eq!(result, None);
+    }
+
+    #[test]
+    fn test_union_nulls_one_side() {
+        let all_valid = NullBuffer::from(&[true, true, true, true]);
+        let all_null = NullBuffer::from(&[false, false, false, false]);
+
+        let result = NullBuffer::union(Some(&all_valid), Some(&all_null));
+        assert_eq!(result, Some(all_null.clone()));
+
+        let result = NullBuffer::union(Some(&all_null), Some(&all_valid));
+        assert_eq!(result, Some(all_null.clone()));
+    }
 }
diff --git a/arrow-string/src/concat_elements.rs 
b/arrow-string/src/concat_elements.rs
index 4809cc5e10..8493dfafe0 100644
--- a/arrow-string/src/concat_elements.rs
+++ b/arrow-string/src/concat_elements.rs
@@ -16,15 +16,15 @@
 // under the License.
 
 //! Provides utility functions for concatenation of elements in arrays.
+
+use std::marker::PhantomData;
 use std::sync::Arc;
 
-use arrow_array::builder::{
-    BinaryViewBuilder, BufferBuilder, FixedSizeBinaryBuilder, 
StringViewBuilder,
-};
-use arrow_array::types::ByteArrayType;
+use arrow_array::builder::{BufferBuilder, FixedSizeBinaryBuilder, make_view};
+use arrow_array::types::{ByteArrayType, ByteViewType};
 use arrow_array::*;
-use arrow_buffer::{ArrowNativeType, MutableBuffer, NullBuffer};
-use arrow_data::ArrayDataBuilder;
+use arrow_buffer::{ArrowNativeType, Buffer, MutableBuffer, NullBuffer, 
ScalarBuffer};
+use arrow_data::{ArrayDataBuilder, MAX_INLINE_VIEW_LEN};
 use arrow_schema::{ArrowError, DataType};
 
 /// Returns the elementwise concatenation of a [`GenericByteArray`].
@@ -221,6 +221,158 @@ pub fn concat_elements_fixed_size_binary(
     Ok(result.finish())
 }
 
+struct ConcatByteViewBuilder<T>
+where
+    T: ByteViewType,
+{
+    views: Vec<u128>,
+    data: Vec<u8>,
+    inline: Vec<u8>,
+    phantom: PhantomData<T>,
+}
+
+impl<T> ConcatByteViewBuilder<T>
+where
+    T: ByteViewType,
+{
+    /// Returns the elementwise concatenation of two [`GenericByteViewArray`]s.
+    fn concat_elements_view_array(
+        left: &GenericByteViewArray<T>,
+        right: &GenericByteViewArray<T>,
+    ) -> Result<GenericByteViewArray<T>, ArrowError> {
+        let len = left.len();
+        if len != right.len() {
+            return Err(ArrowError::ComputeError(format!(
+                "Arrays must have the same length: {} != {}",
+                len,
+                right.len()
+            )));
+        }
+
+        let null_buffer = NullBuffer::union(left.nulls(), right.nulls());
+
+        // Compute the required data buffer size, excluding any elements that 
are null
+        // or are small enough to be stored inline.
+        let data_size = match &null_buffer {
+            None => left
+                .lengths()
+                .zip(right.lengths())
+                .map(|(l, r)| l + r)
+                .filter(|len| *len > MAX_INLINE_VIEW_LEN)
+                .map(|len| len as usize)
+                .sum(),
+            Some(nb) => left
+                .lengths()
+                .zip(right.lengths())
+                .zip(nb.iter())
+                .filter(|((_, _), not_null)| *not_null)
+                .map(|((l, r), _)| l + r)
+                .filter(|len| *len > MAX_INLINE_VIEW_LEN)
+                .map(|len| len as usize)
+                .sum(),
+        };
+
+        if data_size > i32::MAX as usize {
+            return Err(ArrowError::ArithmeticOverflow(
+                "byte array offset overflow".to_string(),
+            ));
+        }
+        let mut builder = Self::with_capacity(len, data_size);
+
+        match &null_buffer {
+            None => {
+                for (l, r) in left.bytes_iter().zip(right.bytes_iter()) {
+                    builder.append_concat_view(l, r);
+                }
+            }
+            Some(nb) => {
+                for ((l, r), not_null) in 
left.bytes_iter().zip(right.bytes_iter()).zip(nb.iter()) {
+                    if not_null {
+                        builder.append_concat_view(l, r);
+                    } else {
+                        builder.append_empty_view();
+                    }
+                }
+            }
+        };
+
+        builder.finish(null_buffer)
+    }
+
+    fn with_capacity(item_capacity: usize, data_capacity: usize) -> Self {
+        Self {
+            views: Vec::with_capacity(item_capacity),
+            data: Vec::with_capacity(data_capacity),
+            inline: Vec::with_capacity(MAX_INLINE_VIEW_LEN as usize),
+            phantom: PhantomData,
+        }
+    }
+
+    /// Append a view containing the concatenation of `left` and `right`.
+    fn append_concat_view(&mut self, left: &[u8], right: &[u8]) {
+        let total_len = left.len() + right.len();
+        if total_len > MAX_INLINE_VIEW_LEN as usize {
+            let offset = self.data.len();
+
+            // SAFETY: we've checked that the total data size is within 
i32::MAX
+            // in `concat_elements_view_array`, so offset cannot exceed it.
+            // Not using `u32::try_from` on each insertion makes a ~5% 
difference
+            // in benchmarking
+            debug_assert!(offset <= i32::MAX as usize);
+            let view_offset: u32 = offset as u32;
+
+            self.data.extend_from_slice(left);
+            self.data.extend_from_slice(right);
+            self.views
+                .push(make_view(&self.data[offset..], 0, view_offset));
+        } else {
+            self.inline.extend_from_slice(left);
+            self.inline.extend_from_slice(right);
+            self.views.push(make_view(&self.inline, 0, 0));
+            self.inline.clear();
+        };
+    }
+
+    /// Append an empty view.
+    #[inline]
+    fn append_empty_view(&mut self) {
+        self.views.push(0);
+    }
+
+    fn finish(
+        self,
+        null_buffer: Option<NullBuffer>,
+    ) -> Result<GenericByteViewArray<T>, ArrowError> {
+        if let Some(ref nulls) = null_buffer {
+            if nulls.len() != self.views.len() {
+                return Err(ArrowError::ComputeError(format!(
+                    "Null buffer length ({}) must match row count ({})",
+                    nulls.len(),
+                    self.views.len()
+                )));
+            }
+        }
+
+        let buffers = if self.data.is_empty() {
+            Arc::from([])
+        } else {
+            Arc::from([Buffer::from(self.data)])
+        };
+
+        // SAFETY: views were constructed with correct lengths, offsets, and
+        // prefixes. UTF-8 validity is implicitly guaranteed by never 
concatenating
+        // arrays with mixed ByteViewTypes.
+        let array = unsafe {
+            GenericByteViewArray::<T>::new_unchecked(
+                ScalarBuffer::from(self.views),
+                buffers,
+                null_buffer,
+            )
+        };
+        Ok(array)
+    }
+}
+
 /// Concatenates two `BinaryViewArray`s element-wise.
 /// If either element is `Null`, the result element is also `Null`.
 ///
@@ -231,32 +383,7 @@ pub fn concat_elements_binary_view_array(
     left: &BinaryViewArray,
     right: &BinaryViewArray,
 ) -> Result<BinaryViewArray, ArrowError> {
-    if left.len() != right.len() {
-        return Err(ArrowError::ComputeError(format!(
-            "Arrays must have the same length: {} != {}",
-            left.len(),
-            right.len()
-        )));
-    }
-    let mut result = BinaryViewBuilder::with_capacity(left.len());
-
-    // Avoid reallocations by writing to a reused buffer
-    let mut buffer = MutableBuffer::new(0);
-
-    // Pre-compute combined null bitmap, so the per-row NULL check is efficient
-    let nulls = NullBuffer::union(left.nulls(), right.nulls());
-
-    for i in 0..left.len() {
-        if nulls.as_ref().is_some_and(|n| n.is_null(i)) {
-            result.append_null();
-        } else {
-            buffer.clear();
-            buffer.extend_from_slice(left.value(i));
-            buffer.extend_from_slice(right.value(i));
-            result.try_append_value(&buffer)?;
-        }
-    }
-    Ok(result.finish())
+    ConcatByteViewBuilder::concat_elements_view_array(left, right)
 }
 
 /// Concatenates two `StringViewArray`s element-wise.
@@ -266,41 +393,11 @@ pub fn concat_elements_binary_view_array(
 /// - Returns an error if the input arrays have different lengths.
 /// - Returns an error if any concatenated value exceeds `u32::MAX` in length.
 /// - Returns an error if concatenated strings do not result in a proper UTF-8 
string
-// Cannot reuse code with `GenericByteViewBuilder` since `try_append_value` 
works with
-// `AsRef<T::Native>`, and there is no conversion from `ByteViewType` to this 
or [u8]
 pub fn concat_elements_string_view_array(
     left: &StringViewArray,
     right: &StringViewArray,
 ) -> Result<StringViewArray, ArrowError> {
-    if left.len() != right.len() {
-        return Err(ArrowError::ComputeError(format!(
-            "Arrays must have the same length: {} != {}",
-            left.len(),
-            right.len()
-        )));
-    }
-
-    let mut result = StringViewBuilder::with_capacity(left.len());
-
-    // Avoid reallocations by writing to a reused buffer
-    let mut buffer: Vec<u8> = Vec::new();
-
-    let nulls = NullBuffer::union(left.nulls(), right.nulls());
-
-    for i in 0..left.len() {
-        if nulls.as_ref().is_some_and(|n| n.is_null(i)) {
-            result.append_null();
-        } else {
-            buffer.clear();
-            buffer.extend_from_slice(left.value(i).as_bytes());
-            buffer.extend_from_slice(right.value(i).as_bytes());
-            let s = std::str::from_utf8(&buffer).map_err(|_| {
-                ArrowError::ComputeError("Concatenated values are not valid 
UTF-8".into())
-            })?;
-            result.try_append_value(s)?;
-        }
-    }
-    Ok(result.finish())
+    ConcatByteViewBuilder::concat_elements_view_array(left, right)
 }
 
 /// Returns the elementwise concatenation of [`Array`]s.
@@ -563,23 +660,102 @@ mod tests {
 
     #[test]
     fn test_binary_view_concat() {
-        let left = BinaryViewArray::from_iter(vec![Some(b"foo" as &[u8]), 
Some(b"bar"), None]);
-        let right = BinaryViewArray::from_iter(vec![None, Some(b"yyy" as 
&[u8]), Some(b"zzz")]);
+        let long = b"ThisStringIsLongerThan12Bytes" as &[u8];
+        let left = BinaryViewArray::from_iter(vec![
+            Some(b"foo" as &[u8]),
+            Some(b"bar"),
+            None,
+            Some(b"foofoofoo"),
+            Some(b"foo"),
+            Some(long),
+            Some(long),
+        ]);
+        let right = BinaryViewArray::from_iter(vec![
+            None,
+            Some(b"yyy" as &[u8]),
+            Some(b"zzz"),
+            Some(b"barbarbar"),
+            Some(long),
+            Some(b"bar"),
+            Some(long),
+        ]);
 
         let output = concat_elements_binary_view_array(&left, &right).unwrap();
 
-        let expected = BinaryViewArray::from_iter(vec![None, Some(b"baryyy" as 
&[u8]), None]);
+        let expected = BinaryViewArray::from_iter(vec![
+            None,
+            Some(b"baryyy" as &[u8]),
+            None,
+            Some(b"foofoofoobarbarbar"),
+            Some(b"fooThisStringIsLongerThan12Bytes"),
+            Some(b"ThisStringIsLongerThan12Bytesbar"),
+            
Some(b"ThisStringIsLongerThan12BytesThisStringIsLongerThan12Bytes"),
+        ]);
         assert_eq!(output, expected);
     }
 
     #[test]
     fn test_string_view_concat() {
-        let left = StringViewArray::from_iter(vec![Some("foo"), Some("bar"), 
None]);
-        let right = StringViewArray::from_iter(vec![None, Some("yyy"), 
Some("zzz")]);
+        let long = "ThisStringIsLongerThan12Bytes";
+        let left = StringViewArray::from_iter(vec![
+            Some("foo"),
+            Some("bar"),
+            None,
+            Some("foofoofoo"),
+            Some("foo"),
+            Some(long),
+            Some(long),
+        ]);
+        let right = StringViewArray::from_iter(vec![
+            None,
+            Some("yyy"),
+            Some("zzz"),
+            Some("barbarbar"),
+            Some(long),
+            Some("bar"),
+            Some(long),
+        ]);
+
+        let output = concat_elements_string_view_array(&left, &right).unwrap();
+
+        let expected = StringViewArray::from_iter(vec![
+            None,
+            Some("baryyy"),
+            None,
+            Some("foofoofoobarbarbar"),
+            Some("fooThisStringIsLongerThan12Bytes"),
+            Some("ThisStringIsLongerThan12Bytesbar"),
+            Some("ThisStringIsLongerThan12BytesThisStringIsLongerThan12Bytes"),
+        ]);
+        assert_eq!(output, expected);
+
+        let left = StringViewArray::from_iter(vec![
+            Some("a"),
+            Some("b"),
+            Some("foofoofoo"),
+            Some("a"),
+            Some(long),
+            Some(long),
+        ]);
+        let right = StringViewArray::from_iter(vec![
+            Some("c"),
+            Some("d"),
+            Some("barbarbar"),
+            Some(long),
+            Some("d"),
+            Some(long),
+        ]);
 
         let output = concat_elements_string_view_array(&left, &right).unwrap();
 
-        let expected = StringViewArray::from_iter(vec![None, Some("baryyy"), 
None]);
+        let expected = StringViewArray::from_iter(vec![
+            Some("ac"),
+            Some("bd"),
+            Some("foofoofoobarbarbar"),
+            Some("aThisStringIsLongerThan12Bytes"),
+            Some("ThisStringIsLongerThan12Bytesd"),
+            Some("ThisStringIsLongerThan12BytesThisStringIsLongerThan12Bytes"),
+        ]);
         assert_eq!(output, expected);
     }
 
@@ -676,13 +852,73 @@ mod tests {
         assert_eq!(output, expected);
 
         // test for BinaryViewArray
-        let left = BinaryViewArray::from_iter(vec![Some(b"foo" as &[u8]), 
Some(b"bar"), None]);
-        let right = BinaryViewArray::from_iter(vec![None, Some(b"yyy" as 
&[u8]), Some(b"zzz")]);
+        let long = b"ThisStringIsLongerThan12Bytes" as &[u8];
+        let left = BinaryViewArray::from_iter(vec![
+            Some(b"foo" as &[u8]),
+            Some(b"bar"),
+            None,
+            Some(b"foofoofoo"),
+            Some(b"foo"),
+            Some(long),
+            Some(long),
+        ]);
+        let right = BinaryViewArray::from_iter(vec![
+            None,
+            Some(b"yyy" as &[u8]),
+            Some(b"zzz"),
+            Some(b"barbarbar"),
+            Some(long),
+            Some(b"bar"),
+            Some(long),
+        ]);
         let output: BinaryViewArray = concat_elements_dyn(&left, &right)
             .unwrap()
             .into_data()
             .into();
-        let expected = BinaryViewArray::from_iter(vec![None, Some(b"baryyy" as 
&[u8]), None]);
+        let expected = BinaryViewArray::from_iter(vec![
+            None,
+            Some(b"baryyy" as &[u8]),
+            None,
+            Some(b"foofoofoobarbarbar"),
+            Some(b"fooThisStringIsLongerThan12Bytes"),
+            Some(b"ThisStringIsLongerThan12Bytesbar"),
+            
Some(b"ThisStringIsLongerThan12BytesThisStringIsLongerThan12Bytes"),
+        ]);
+        assert_eq!(output, expected);
+
+        // test for StringViewArray
+        let long = "ThisStringIsLongerThan12Bytes";
+        let left = StringViewArray::from_iter(vec![
+            Some("foo"),
+            Some("bar"),
+            None,
+            Some("foofoofoo"),
+            Some("foo"),
+            Some(long),
+            Some(long),
+        ]);
+        let right = StringViewArray::from_iter(vec![
+            None,
+            Some("yyy"),
+            Some("zzz"),
+            Some("barbarbar"),
+            Some(long),
+            Some("bar"),
+            Some(long),
+        ]);
+        let output: StringViewArray = concat_elements_dyn(&left, &right)
+            .unwrap()
+            .into_data()
+            .into();
+        let expected = StringViewArray::from_iter(vec![
+            None,
+            Some("baryyy"),
+            None,
+            Some("foofoofoobarbarbar"),
+            Some("fooThisStringIsLongerThan12Bytes"),
+            Some("ThisStringIsLongerThan12Bytesbar"),
+            Some("ThisStringIsLongerThan12BytesThisStringIsLongerThan12Bytes"),
+        ]);
         assert_eq!(output, expected);
 
         // test for FixedSizeBinaryArray
diff --git a/arrow/Cargo.toml b/arrow/Cargo.toml
index 8e56457ff0..2be4cfd1f1 100644
--- a/arrow/Cargo.toml
+++ b/arrow/Cargo.toml
@@ -242,6 +242,11 @@ required-features = ["test_utils"]
 name = "array_slice"
 harness = false
 
+[[bench]]
+name = "concatenate_elements"
+harness = false
+required-features = ["test_utils"]
+
 [[bench]]
 name = "concatenate_kernel"
 harness = false
diff --git a/arrow/benches/concatenate_elements.rs 
b/arrow/benches/concatenate_elements.rs
new file mode 100644
index 0000000000..0b197a54ce
--- /dev/null
+++ b/arrow/benches/concatenate_elements.rs
@@ -0,0 +1,92 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+extern crate arrow;
+#[macro_use]
+extern crate criterion;
+
+use criterion::Criterion;
+
+use arrow::array::*;
+use arrow::datatypes::*;
+use arrow::util::bench_util::*;
+use arrow_string::concat_elements::concat_elements_dyn;
+use std::hint;
+
+fn bench_concat(v1: &dyn Array, v2: &dyn Array) {
+    hint::black_box(concat_elements_dyn(v1, v2).unwrap());
+}
+
+fn add_benchmark(c: &mut Criterion) {
+    let v1 = create_string_array::<i32>(1024, 0.0);
+    let v2 = create_string_array::<i32>(1024, 0.0);
+    c.bench_function("concat str 1024", |b| b.iter(|| bench_concat(&v1, &v2)));
+
+    let v1 = create_string_array::<i32>(1024, 0.5);
+    let v2 = create_string_array::<i32>(1024, 0.5);
+    c.bench_function("concat str nulls 1024", |b| {
+        b.iter(|| bench_concat(&v1, &v2))
+    });
+
+    {
+        let input1 = create_string_array::<i32>(8192, 0.0);
+        let input2 = create_string_array::<i32>(8192, 0.0);
+        c.bench_function("concat str 8192", |b| {
+            b.iter(|| bench_concat(&input1, &input2))
+        });
+    }
+
+    {
+        let input1 = create_string_array::<i32>(8192, 0.5);
+        let input2 = create_string_array::<i32>(8192, 0.5);
+        c.bench_function("concat str nulls 8192 over 100 arrays", |b| {
+            b.iter(|| bench_concat(&input1, &input2))
+        });
+    }
+
+    // String view arrays
+    for null_density in [0.0, 0.2] {
+        // Any strings less than 12 characters are stored as prefix only, so 
specially
+        // benchmark cases that have different mixes of lengths.
+        for (name, str_len) in [("all_inline", 12), ("", 20), ("", 128)] {
+            let array = create_string_view_array_with_len(8192, null_density, 
str_len, false);
+            let id = format!(
+                "concat utf8_view {name} max_str_len={str_len} 
null_density={null_density}"
+            );
+            c.bench_function(&id, |b| b.iter(|| bench_concat(&array, &array)));
+        }
+    }
+
+    let v1 = create_string_array_with_len::<i32>(10, 0.0, 20);
+    let v1 = create_dict_from_values::<Int32Type>(1024, 0.0, &v1);
+    let v2 = create_string_array_with_len::<i32>(10, 0.0, 20);
+    let v2 = create_dict_from_values::<Int32Type>(1024, 0.0, &v2);
+    c.bench_function("concat str_dict 1024", |b| {
+        b.iter(|| bench_concat(&v1, &v2))
+    });
+
+    let v1 = create_string_array_with_len::<i32>(1024, 0.0, 20);
+    let v1 = create_sparse_dict_from_values::<Int32Type>(1024, 0.0, &v1, 
10..20);
+    let v2 = create_string_array_with_len::<i32>(1024, 0.0, 20);
+    let v2 = create_sparse_dict_from_values::<Int32Type>(1024, 0.0, &v2, 
30..40);
+    c.bench_function("concat str_dict_sparse 1024", |b| {
+        b.iter(|| bench_concat(&v1, &v2))
+    });
+}
+
+criterion_group!(benches, add_benchmark);
+criterion_main!(benches);

Reply via email to