Skip to content

Commit 1dc73dc

Browse files
authored
bench: add date_part benchmark (#23350)
## Which issue does this PR close? - Refers #23351 ## Rationale for this change `date_part` UDF and other datetime functions have non-trivial logic and should be benchmarked ## What changes are included in this PR? Brand new bench for `date_part`. Covers a lot of code paths and input combinations ## Are these changes tested? I've run it locally; it passes. ## Are there any user-facing changes? <!-- If there are user-facing changes then we may require documentation to be updated before approving the PR. --> <!-- If there are any breaking changes to public APIs, please add the `api change` label. -->
1 parent 1c1a78a commit 1dc73dc

2 files changed

Lines changed: 354 additions & 0 deletions

File tree

datafusion/functions/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,11 @@ harness = false
182182
name = "date_trunc"
183183
required-features = ["datetime_expressions"]
184184

185+
[[bench]]
186+
harness = false
187+
name = "date_part"
188+
required-features = ["datetime_expressions"]
189+
185190
[[bench]]
186191
harness = false
187192
name = "to_char"
Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
use std::hint::black_box;
19+
use std::sync::Arc;
20+
21+
use arrow::array::types::{IntervalDayTime, IntervalMonthDayNano};
22+
use arrow::array::{
23+
Array, ArrayRef, Date32Array, Date64Array, DurationNanosecondArray,
24+
IntervalDayTimeArray, IntervalMonthDayNanoArray, IntervalYearMonthArray,
25+
Time32MillisecondArray, Time32SecondArray, Time64MicrosecondArray,
26+
Time64NanosecondArray, TimestampMicrosecondArray, TimestampMillisecondArray,
27+
TimestampNanosecondArray, TimestampSecondArray,
28+
};
29+
use arrow::datatypes::{DataType, Field};
30+
use criterion::{Criterion, criterion_group, criterion_main};
31+
use datafusion_common::ScalarValue;
32+
use datafusion_common::config::ConfigOptions;
33+
use datafusion_expr::{ColumnarValue, ScalarFunctionArgs, ScalarUDF};
34+
use datafusion_functions::datetime::date_part;
35+
use rand::prelude::StdRng;
36+
use rand::{Rng, SeedableRng};
37+
38+
const BATCH_SIZE: usize = 1000;
39+
const TS_BOUND: i64 = 2_006_463_600;
40+
const SEC_DAY: i64 = 86_400;
41+
const DAYS_SINCE_EPOCH: i64 = TS_BOUND / SEC_DAY;
42+
43+
fn generate_timestamp_ns_array(rng: &mut StdRng) -> TimestampNanosecondArray {
44+
TimestampNanosecondArray::from(
45+
(0..BATCH_SIZE)
46+
.map(|_| rng.random_range(0..TS_BOUND * 1_000_000_000))
47+
.collect::<Vec<_>>(),
48+
)
49+
}
50+
51+
fn generate_timestamp_us_array(rng: &mut StdRng) -> TimestampMicrosecondArray {
52+
TimestampMicrosecondArray::from(
53+
(0..BATCH_SIZE)
54+
.map(|_| rng.random_range(0..TS_BOUND * 1_000_000))
55+
.collect::<Vec<_>>(),
56+
)
57+
}
58+
59+
fn generate_timestamp_ms_array(rng: &mut StdRng) -> TimestampMillisecondArray {
60+
TimestampMillisecondArray::from(
61+
(0..BATCH_SIZE)
62+
.map(|_| rng.random_range(0..TS_BOUND * 1_000))
63+
.collect::<Vec<_>>(),
64+
)
65+
}
66+
67+
fn generate_timestamp_s_array(rng: &mut StdRng) -> TimestampSecondArray {
68+
TimestampSecondArray::from(
69+
(0..BATCH_SIZE)
70+
.map(|_| rng.random_range(0..TS_BOUND))
71+
.collect::<Vec<_>>(),
72+
)
73+
}
74+
75+
fn generate_date32_array(rng: &mut StdRng) -> Date32Array {
76+
// Provide days since epoch
77+
Date32Array::from(
78+
(0..BATCH_SIZE)
79+
.map(|_| rng.random_range(0..DAYS_SINCE_EPOCH as i32))
80+
.collect::<Vec<_>>(),
81+
)
82+
}
83+
84+
fn generate_date64_array(rng: &mut StdRng) -> Date64Array {
85+
// Provide milliseconds since epoch aligned to day boundaries
86+
Date64Array::from(
87+
(0..BATCH_SIZE)
88+
.map(|_| rng.random_range(0..DAYS_SINCE_EPOCH) * SEC_DAY * 1_000)
89+
.collect::<Vec<_>>(),
90+
)
91+
}
92+
93+
fn generate_time32_second_array(rng: &mut StdRng) -> Time32SecondArray {
94+
Time32SecondArray::from(
95+
(0..BATCH_SIZE)
96+
.map(|_| rng.random_range(0..SEC_DAY as i32))
97+
.collect::<Vec<_>>(),
98+
)
99+
}
100+
101+
fn generate_time32_millisecond_array(rng: &mut StdRng) -> Time32MillisecondArray {
102+
Time32MillisecondArray::from(
103+
(0..BATCH_SIZE)
104+
.map(|_| rng.random_range(0..(SEC_DAY * 1_000) as i32))
105+
.collect::<Vec<_>>(),
106+
)
107+
}
108+
109+
fn generate_time64_microsecond_array(rng: &mut StdRng) -> Time64MicrosecondArray {
110+
Time64MicrosecondArray::from(
111+
(0..BATCH_SIZE)
112+
.map(|_| rng.random_range(0..SEC_DAY * 1_000_000))
113+
.collect::<Vec<_>>(),
114+
)
115+
}
116+
117+
fn generate_time64_nanosecond_array(rng: &mut StdRng) -> Time64NanosecondArray {
118+
Time64NanosecondArray::from(
119+
(0..BATCH_SIZE)
120+
.map(|_| rng.random_range(0..SEC_DAY * 1_000_000_000))
121+
.collect::<Vec<_>>(),
122+
)
123+
}
124+
125+
fn generate_interval_year_month_array(rng: &mut StdRng) -> IntervalYearMonthArray {
126+
let years = 10;
127+
IntervalYearMonthArray::from(
128+
(0..BATCH_SIZE)
129+
.map(|_| rng.random_range(0..12 * years))
130+
.collect::<Vec<_>>(),
131+
)
132+
}
133+
134+
fn generate_interval_day_time_array(rng: &mut StdRng) -> IntervalDayTimeArray {
135+
IntervalDayTimeArray::from(
136+
(0..BATCH_SIZE)
137+
.map(|_| IntervalDayTime {
138+
days: rng.random_range(0..365),
139+
milliseconds: rng.random_range(0..(SEC_DAY * 1_000) as i32),
140+
})
141+
.collect::<Vec<_>>(),
142+
)
143+
}
144+
145+
fn generate_interval_mdn_array(rng: &mut StdRng) -> IntervalMonthDayNanoArray {
146+
IntervalMonthDayNanoArray::from(
147+
(0..BATCH_SIZE)
148+
.map(|_| IntervalMonthDayNano {
149+
months: rng.random_range(0..12),
150+
days: rng.random_range(0..365),
151+
nanoseconds: rng.random_range(0..SEC_DAY * 1_000_000_000),
152+
})
153+
.collect::<Vec<_>>(),
154+
)
155+
}
156+
157+
fn generate_duration_nanosecond_array(rng: &mut StdRng) -> DurationNanosecondArray {
158+
DurationNanosecondArray::from(
159+
(0..BATCH_SIZE)
160+
.map(|_| rng.random_range(0..TS_BOUND * 1_000_000_000))
161+
.collect::<Vec<_>>(),
162+
)
163+
}
164+
165+
fn bench_date_part(
166+
c: &mut Criterion,
167+
udf: &Arc<ScalarUDF>,
168+
bench_name: &str,
169+
part: &str,
170+
array: ArrayRef,
171+
return_type: DataType,
172+
) {
173+
let batch_len = array.len();
174+
let part_cv = ColumnarValue::Scalar(ScalarValue::Utf8(Some(part.to_string())));
175+
let array_cv = ColumnarValue::Array(array);
176+
let return_field = Arc::new(Field::new("date_part", return_type, true));
177+
let arg_fields = vec![
178+
Field::new("a", part_cv.data_type(), true).into(),
179+
Field::new("b", array_cv.data_type(), true).into(),
180+
];
181+
let config_options = Arc::new(ConfigOptions::default());
182+
183+
c.bench_function(bench_name, |b| {
184+
b.iter(|| {
185+
black_box(
186+
udf.invoke_with_args(ScalarFunctionArgs {
187+
args: vec![part_cv.clone(), array_cv.clone()],
188+
arg_fields: arg_fields.clone(),
189+
number_rows: batch_len,
190+
return_field: Arc::clone(&return_field),
191+
config_options: Arc::clone(&config_options),
192+
})
193+
.expect("date_part should work on valid values"),
194+
)
195+
})
196+
});
197+
}
198+
199+
fn criterion_benchmark(c: &mut Criterion) {
200+
let mut rng = StdRng::seed_from_u64(42);
201+
202+
let ts_s = Arc::new(generate_timestamp_s_array(&mut rng)) as ArrayRef;
203+
let ts_ms = Arc::new(generate_timestamp_ms_array(&mut rng)) as ArrayRef;
204+
let ts_us = Arc::new(generate_timestamp_us_array(&mut rng)) as ArrayRef;
205+
let ts_ns = Arc::new(generate_timestamp_ns_array(&mut rng)) as ArrayRef;
206+
let time32_s = Arc::new(generate_time32_second_array(&mut rng)) as ArrayRef;
207+
let time32_ms = Arc::new(generate_time32_millisecond_array(&mut rng)) as ArrayRef;
208+
let time64_us = Arc::new(generate_time64_microsecond_array(&mut rng)) as ArrayRef;
209+
let time64_ns = Arc::new(generate_time64_nanosecond_array(&mut rng)) as ArrayRef;
210+
let interval_ym = Arc::new(generate_interval_year_month_array(&mut rng)) as ArrayRef;
211+
let interval_dt = Arc::new(generate_interval_day_time_array(&mut rng)) as ArrayRef;
212+
let interval_mdn = Arc::new(generate_interval_mdn_array(&mut rng)) as ArrayRef;
213+
let duration_ns = Arc::new(generate_duration_nanosecond_array(&mut rng)) as ArrayRef;
214+
let date32 = Arc::new(generate_date32_array(&mut rng)) as ArrayRef;
215+
let date64 = Arc::new(generate_date64_array(&mut rng)) as ArrayRef;
216+
217+
let udf = date_part();
218+
219+
for part in ["year", "month", "week", "day", "hour", "minute"] {
220+
for (name, array) in
221+
[("s", &ts_s), ("ms", &ts_ms), ("us", &ts_us), ("ns", &ts_ns)]
222+
{
223+
bench_date_part(
224+
c,
225+
&udf,
226+
&format!("date_part_{part}_{name}_1000"),
227+
part,
228+
Arc::clone(array),
229+
DataType::Int32,
230+
);
231+
}
232+
}
233+
for part in ["year", "month", "week", "day"] {
234+
bench_date_part(
235+
c,
236+
&udf,
237+
&format!("date_part_{part}_date32_1000"),
238+
part,
239+
Arc::clone(&date32),
240+
DataType::Int32,
241+
);
242+
bench_date_part(
243+
c,
244+
&udf,
245+
&format!("date_part_{part}_date64_1000"),
246+
part,
247+
Arc::clone(&date64),
248+
DataType::Int32,
249+
);
250+
}
251+
252+
for part in ["second", "millisecond", "microsecond"] {
253+
for (name, array) in
254+
[("s", &ts_s), ("ms", &ts_ms), ("us", &ts_us), ("ns", &ts_ns)]
255+
{
256+
bench_date_part(
257+
c,
258+
&udf,
259+
&format!("date_part_{part}_{name}_1000"),
260+
part,
261+
Arc::clone(array),
262+
DataType::Int32,
263+
);
264+
}
265+
bench_date_part(
266+
c,
267+
&udf,
268+
&format!("date_part_{part}_date32_1000"),
269+
part,
270+
Arc::clone(&date32),
271+
DataType::Int32,
272+
);
273+
bench_date_part(
274+
c,
275+
&udf,
276+
&format!("date_part_{part}_date64_1000"),
277+
part,
278+
Arc::clone(&date64),
279+
DataType::Int32,
280+
);
281+
}
282+
283+
for (name, array) in [("s", &ts_s), ("ms", &ts_ms), ("us", &ts_us), ("ns", &ts_ns)] {
284+
bench_date_part(
285+
c,
286+
&udf,
287+
&format!("date_part_nanosecond_{name}_1000"),
288+
"nanosecond",
289+
Arc::clone(array),
290+
DataType::Int64,
291+
);
292+
}
293+
bench_date_part(
294+
c,
295+
&udf,
296+
"date_part_nanosecond_date32_1000",
297+
"nanosecond",
298+
Arc::clone(&date32),
299+
DataType::Int64,
300+
);
301+
bench_date_part(
302+
c,
303+
&udf,
304+
"date_part_nanosecond_date64_1000",
305+
"nanosecond",
306+
Arc::clone(&date64),
307+
DataType::Int64,
308+
);
309+
310+
for (name, array) in [
311+
("s", &ts_s),
312+
("ms", &ts_ms),
313+
("us", &ts_us),
314+
("ns", &ts_ns),
315+
("date32", &date32),
316+
("date64", &date64),
317+
("time32_s", &time32_s),
318+
("time32_ms", &time32_ms),
319+
("time64_us", &time64_us),
320+
("time64_ns", &time64_ns),
321+
("interval_ym", &interval_ym),
322+
("interval_dt", &interval_dt),
323+
("interval_mdn", &interval_mdn),
324+
("duration_ns", &duration_ns),
325+
] {
326+
bench_date_part(
327+
c,
328+
&udf,
329+
&format!("date_part_epoch_{name}_1000"),
330+
"epoch",
331+
Arc::clone(array),
332+
DataType::Float64,
333+
);
334+
}
335+
336+
for part in ["quarter", "isoyear", "doy", "dow", "isodow"] {
337+
bench_date_part(
338+
c,
339+
&udf,
340+
&format!("date_part_{part}_timestamp_ns_1000"),
341+
part,
342+
Arc::clone(&ts_ns),
343+
DataType::Int32,
344+
);
345+
}
346+
}
347+
348+
criterion_group!(benches, criterion_benchmark);
349+
criterion_main!(benches);

0 commit comments

Comments
 (0)