Skip to content

Commit 0726fb9

Browse files
authored
Add benchmarks for decimal casting (#8569)
Minimal set of benchmarks to make sure we don't regress in the future
1 parent d300645 commit 0726fb9

2 files changed

Lines changed: 111 additions & 0 deletions

File tree

vortex-array/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,10 @@ harness = false
114114
name = "cast_primitive"
115115
harness = false
116116

117+
[[bench]]
118+
name = "cast_decimal"
119+
harness = false
120+
117121
[[bench]]
118122
name = "search_sorted"
119123
harness = false
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3+
4+
//! Benchmarks for decimal-to-decimal casting across the two dimensions that drive its cost:
5+
//!
6+
//! - **validity**: non-nullable vs nullable inputs. Nullable inputs route the rescale through the
7+
//! masked kernel (`try_map_masked_into`) instead of the dense one (`try_map_into`).
8+
//! - **work**: an *in-place* cast that only widens precision at the same scale, so the values
9+
//! buffer is reused untouched (`O(1)`), vs a *copy* cast that must allocate a new buffer and
10+
//! rescan every value (`O(n)`).
11+
12+
#![expect(clippy::unwrap_used)]
13+
14+
use std::sync::LazyLock;
15+
16+
use divan::Bencher;
17+
use rand::prelude::*;
18+
use vortex_array::ArrayRef;
19+
use vortex_array::Canonical;
20+
use vortex_array::IntoArray;
21+
use vortex_array::VortexSessionExecute;
22+
use vortex_array::arrays::DecimalArray;
23+
use vortex_array::builtins::ArrayBuiltins;
24+
use vortex_array::dtype::DType;
25+
use vortex_array::dtype::DecimalDType;
26+
use vortex_array::dtype::Nullability;
27+
use vortex_array::validity::Validity;
28+
use vortex_buffer::BufferMut;
29+
use vortex_session::VortexSession;
30+
31+
fn main() {
32+
divan::main();
33+
}
34+
35+
// Kept small enough to stay in L2 so the kernel cost shows up rather than DRAM bandwidth.
36+
const SIZES: &[usize] = &[65_536];
37+
38+
static SESSION: LazyLock<VortexSession> = LazyLock::new(vortex_array::array_session);
39+
40+
/// Builds an `i64`-backed `Decimal(precision, scale)` array of `n` values that all fit in
41+
/// precision 10, optionally with ~50% nulls.
42+
fn decimal_array(n: usize, precision: u8, scale: i8, nullable: bool) -> ArrayRef {
43+
let mut rng = StdRng::seed_from_u64(42);
44+
let values: BufferMut<i64> = (0..n)
45+
.map(|_| rng.random_range(0..1_000_000_000i64))
46+
.collect();
47+
let validity = if nullable {
48+
Validity::from_iter((0..n).map(|_| rng.random_bool(0.5)))
49+
} else {
50+
Validity::NonNullable
51+
};
52+
DecimalArray::new(
53+
values.freeze(),
54+
DecimalDType::new(precision, scale),
55+
validity,
56+
)
57+
.into_array()
58+
}
59+
60+
/// Casts `array` to `target` and forces execution, the unit measured by every bench below.
61+
fn bench_cast(bencher: Bencher, array: ArrayRef, target: DType) {
62+
bencher
63+
.with_inputs(|| (array.clone(), SESSION.create_execution_ctx()))
64+
.bench_refs(|(a, ctx)| a.cast(target.clone()).unwrap().execute::<Canonical>(ctx));
65+
}
66+
67+
// In place: widening precision at the same scale keeps the physical type, so the cast reuses the
68+
// values buffer untouched.
69+
70+
#[divan::bench(args = SIZES)]
71+
fn in_place_non_nullable(bencher: Bencher, n: usize) {
72+
bench_cast(
73+
bencher,
74+
decimal_array(n, 10, 2, false),
75+
DType::Decimal(DecimalDType::new(18, 2), Nullability::NonNullable),
76+
);
77+
}
78+
79+
#[divan::bench(args = SIZES)]
80+
fn in_place_nullable(bencher: Bencher, n: usize) {
81+
bench_cast(
82+
bencher,
83+
decimal_array(n, 10, 2, true),
84+
DType::Decimal(DecimalDType::new(18, 2), Nullability::Nullable),
85+
);
86+
}
87+
88+
// Copy: narrowing precision at the same scale cannot reuse the buffer; every value is re-scanned
89+
// and range-checked into a freshly allocated buffer.
90+
91+
#[divan::bench(args = SIZES)]
92+
fn copy_non_nullable(bencher: Bencher, n: usize) {
93+
bench_cast(
94+
bencher,
95+
decimal_array(n, 18, 2, false),
96+
DType::Decimal(DecimalDType::new(10, 2), Nullability::NonNullable),
97+
);
98+
}
99+
100+
#[divan::bench(args = SIZES)]
101+
fn copy_nullable(bencher: Bencher, n: usize) {
102+
bench_cast(
103+
bencher,
104+
decimal_array(n, 18, 2, true),
105+
DType::Decimal(DecimalDType::new(10, 2), Nullability::Nullable),
106+
);
107+
}

0 commit comments

Comments
 (0)