Skip to content

Commit a412ae8

Browse files
committed
bench: Use variable-length inputs and scalar n in left/right benchmark
The Utf8 inputs were all exactly 32 bytes, while the Utf8View inputs used a different length distribution, so the two array types were not benchmarked on the same data. Fixed-length inputs also make per-row work that depends on the input length, such as an ASCII check over the whole string, perfectly predictable, so it looks nearly free. And every case passed `n` as an array cycling through a short range, although `n` is usually a literal. Generate variable-length inputs for every case and build the Utf8 and Utf8View arrays from the same strings. Pass `n` as a scalar, and add a case with a different random `n` per row. Add cases for short results from long inputs, for `n` exceeding the input length, and for negative `n`. Also derive the return field from the function instead of always using Utf8View. Since #23330, `left` and `right` return Utf8 for Utf8 input, and the mismatch fails the check in `ScalarUDF::invoke_with_args` when debug assertions are enabled.
1 parent 9547b09 commit a412ae8

1 file changed

Lines changed: 70 additions & 32 deletions

File tree

‎datafusion/functions/benches/unicode_expressions/left_right.rs‎

Lines changed: 70 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,55 +16,82 @@
1616
// under the License.
1717

1818
use std::hint::black_box;
19-
use std::ops::Range;
19+
use std::ops::RangeInclusive;
2020
use std::sync::Arc;
2121

22-
use arrow::array::{ArrayRef, Int64Array};
23-
use arrow::datatypes::{DataType, Field};
24-
use arrow::util::bench_util::{
25-
create_string_array_with_len, create_string_view_array_with_len,
26-
};
22+
use arrow::array::{Int64Array, StringViewArray};
23+
use arrow::datatypes::Field;
24+
use arrow::util::bench_util::create_string_array_with_len_range_and_prefix_and_seed;
2725
use criterion::{Criterion, criterion_group};
26+
use datafusion_common::ScalarValue;
2827
use datafusion_common::config::ConfigOptions;
29-
use datafusion_expr::{ColumnarValue, ScalarFunctionArgs};
28+
use datafusion_expr::{ColumnarValue, ReturnFieldArgs, ScalarFunctionArgs};
3029
use datafusion_functions::unicode::{left, right};
30+
use rand::rngs::StdRng;
31+
use rand::{Rng, SeedableRng};
3132

3233
const BATCH_SIZE: usize = 8192;
3334

35+
/// How the `n` argument is passed.
36+
enum NArg {
37+
/// The same `n` for every row, as in `left(s, 5)`.
38+
Scalar(i64),
39+
/// A different `n` for each row, drawn from the range.
40+
PerRow(RangeInclusive<i64>),
41+
}
42+
3443
fn create_args(
35-
str_len: usize,
36-
n_range: Range<i64>,
44+
str_len: RangeInclusive<usize>,
45+
n: &NArg,
3746
is_string_view: bool,
3847
) -> Vec<ColumnarValue> {
48+
let strings = create_string_array_with_len_range_and_prefix_and_seed::<i32>(
49+
BATCH_SIZE,
50+
0.1,
51+
*str_len.start(),
52+
*str_len.end(),
53+
"",
54+
42,
55+
);
3956
let string_arg = if is_string_view {
40-
ColumnarValue::Array(Arc::new(create_string_view_array_with_len(
41-
BATCH_SIZE, 0.1, str_len, true,
42-
)))
57+
ColumnarValue::Array(Arc::new(strings.iter().collect::<StringViewArray>()))
4358
} else {
44-
ColumnarValue::Array(Arc::new(create_string_array_with_len::<i32>(
45-
BATCH_SIZE, 0.1, str_len,
46-
)))
59+
ColumnarValue::Array(Arc::new(strings))
4760
};
4861

49-
let n_span = (n_range.end - n_range.start) as usize;
50-
let n_values: Vec<i64> = (0..BATCH_SIZE)
51-
.map(|i| n_range.start + (i % n_span) as i64)
52-
.collect();
53-
let n_array = Arc::new(Int64Array::from(n_values));
62+
let n_arg = match n {
63+
NArg::Scalar(n) => ColumnarValue::Scalar(ScalarValue::Int64(Some(*n))),
64+
NArg::PerRow(range) => {
65+
let mut rng = StdRng::seed_from_u64(42);
66+
let n_values = Int64Array::from_iter_values(
67+
(0..BATCH_SIZE).map(|_| rng.random_range(range.clone())),
68+
);
69+
ColumnarValue::Array(Arc::new(n_values))
70+
}
71+
};
5472

55-
vec![
56-
string_arg,
57-
ColumnarValue::Array(Arc::clone(&n_array) as ArrayRef),
58-
]
73+
vec![string_arg, n_arg]
5974
}
6075

6176
fn criterion_benchmark(c: &mut Criterion) {
62-
// Short results (1-10 chars) produce inline StringView entries (≤12 bytes).
63-
// Long results (20-29 chars) produce out-of-line entries.
77+
// Input lengths vary within each case, as in real data: with fixed-length
78+
// inputs, per-row work that depends on the input length is unrealistically
79+
// predictable.
6480
let cases = [
65-
("short_result", 32, 1..11_i64),
66-
("long_result", 32, 20..30_i64),
81+
// Results of up to 5 chars, stored inline in StringView arrays (≤12 bytes).
82+
("short_result", 1..=32, NArg::Scalar(5)),
83+
// 25-char results, stored out of line.
84+
("long_result", 32..=64, NArg::Scalar(25)),
85+
// Short results from long inputs.
86+
("short_result_long_input", 96..=256, NArg::Scalar(5)),
87+
// `n` exceeds every input's length, so each result is the whole input.
88+
("n_exceeds_len", 1..=32, NArg::Scalar(40)),
89+
// Negative `n` removes characters from the other end.
90+
("negative_n", 1..=32, NArg::Scalar(-5)),
91+
// `n` computed per row, as in `left(s, strpos(s, '-') - 1)`.
92+
("per_row_n", 1..=32, NArg::PerRow(1..=10)),
6793
];
94+
let config_options = Arc::new(ConfigOptions::default());
6895

6996
for function in [left(), right()] {
7097
let mut group = c.benchmark_group(function.name().to_string());
@@ -76,18 +103,29 @@ fn criterion_benchmark(c: &mut Criterion) {
76103
"string"
77104
};
78105

79-
for (case_name, str_len, n_range) in &cases {
106+
for (case_name, str_len, n) in &cases {
80107
let bench_name = format!("{array_type} {case_name}");
81-
let args = create_args(*str_len, n_range.clone(), is_string_view);
108+
let args = create_args(str_len.clone(), n, is_string_view);
82109
let arg_fields: Vec<_> = args
83110
.iter()
84111
.enumerate()
85112
.map(|(idx, arg)| {
86113
Field::new(format!("arg_{idx}"), arg.data_type(), true).into()
87114
})
88115
.collect();
89-
let config_options = Arc::new(ConfigOptions::default());
90-
let return_field = Field::new("f", DataType::Utf8View, true).into();
116+
let scalar_arguments: Vec<_> = args
117+
.iter()
118+
.map(|arg| match arg {
119+
ColumnarValue::Scalar(scalar) => Some(scalar),
120+
ColumnarValue::Array(_) => None,
121+
})
122+
.collect();
123+
let return_field = function
124+
.return_field_from_args(ReturnFieldArgs {
125+
arg_fields: &arg_fields,
126+
scalar_arguments: &scalar_arguments,
127+
})
128+
.expect("should resolve return field");
91129

92130
group.bench_function(&bench_name, |b| {
93131
b.iter(|| {

0 commit comments

Comments
 (0)