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);