Skip to content

Commit bf814bb

Browse files
committed
Add scalar function performance baselines
Adds stable CodSpeed baselines for public construction and execution paths across byte length, binary arithmetic, LIKE, tensor scalar functions, and geo operations. The benches avoid RowFn test-harness internals so later implementation changes compare against the production path. Geo distance covers point and polygon columns, including constants and nullable points. Geo predicate coverage adds sparse and mixed validity cases, while its all-overlapping polygon arm uses 1,024 rows because CodSpeed simulates the compiled amd64/AVX2 machine code. Each benchmark binary that allocates inside the timed region uses vendored `mimalloc`. `Normalized` keeps its checked inherent constructor because its normalized-row invariant requires construction-time validation. Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
1 parent a732ded commit bf814bb

15 files changed

Lines changed: 1094 additions & 16 deletions

File tree

‎.github/workflows/codspeed.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ jobs:
5757
- { shard: 6, name: "Encodings 3", packages: "vortex-pco vortex-runend vortex-sequence" }
5858
- { shard: 7, name: "Encodings 4", packages: "vortex-sparse vortex-zigzag vortex-zstd" }
5959
- { shard: 8, name: "Storage formats & row encoding", packages: "vortex-flatbuffers vortex-proto vortex-btrblocks vortex-row" }
60+
- { shard: 9, name: "Tensor & geo", packages: "vortex-tensor vortex-geo" }
6061
name: "Benchmark with Codspeed (Shard #${{ matrix.shard }})"
6162
timeout-minutes: 30
6263
runs-on: >-

‎Cargo.lock‎

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vortex-array/Cargo.toml‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ harness = false
137137
name = "like"
138138
harness = false
139139

140+
[[bench]]
141+
name = "byte_length"
142+
harness = false
143+
140144
[[bench]]
141145
name = "interleave"
142146
harness = false

‎vortex-array/benches/binary_ops.rs‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,30 @@ fn add_i64_nullable(bencher: Bencher) {
5454
bench_primitive(bencher, lhs, rhs, Operator::Add);
5555
}
5656

57+
#[divan::bench]
58+
fn add_i64_constant(bencher: Bencher) {
59+
let lhs = primitive_nonnull(0).into_array();
60+
let rhs = ConstantArray::new(1_000_000i64, LEN).into_array();
61+
62+
bench_primitive(bencher, lhs, rhs, Operator::Add);
63+
}
64+
65+
#[divan::bench]
66+
fn add_i32_nonnull(bencher: Bencher) {
67+
let lhs = primitive_i32_small_nonnull(1).into_array();
68+
let rhs = primitive_i32_small_nonnull(17).into_array();
69+
70+
bench_primitive(bencher, lhs, rhs, Operator::Add);
71+
}
72+
73+
#[divan::bench]
74+
fn add_u32_nonnull(bencher: Bencher) {
75+
let lhs = primitive_u32_small_nonnull(1).into_array();
76+
let rhs = primitive_u32_small_nonnull(17).into_array();
77+
78+
bench_primitive(bencher, lhs, rhs, Operator::Add);
79+
}
80+
5781
#[divan::bench]
5882
fn mul_i64_nonnull(bencher: Bencher) {
5983
let lhs = primitive_small_nonnull(1).into_array();
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3+
4+
//! Baseline throughput for `byte_length` over UTF-8 view arrays.
5+
//!
6+
//! The arms cover inline and out-of-line views, plus nullable out-of-line views. Their names are
7+
//! intended to remain stable across scalar-function implementation changes so CodSpeed can compare
8+
//! them against `develop`.
9+
10+
#![expect(clippy::unwrap_used)]
11+
12+
use std::sync::LazyLock;
13+
14+
use divan::Bencher;
15+
use divan::counter::ItemsCount;
16+
use mimalloc::MiMalloc;
17+
use vortex_array::ArrayRef;
18+
use vortex_array::Canonical;
19+
use vortex_array::IntoArray;
20+
use vortex_array::VortexSessionExecute;
21+
use vortex_array::array_session;
22+
use vortex_array::arrays::MaskedArray;
23+
use vortex_array::arrays::ScalarFnArray;
24+
use vortex_array::arrays::VarBinViewArray;
25+
use vortex_array::scalar_fn::EmptyOptions;
26+
use vortex_array::scalar_fn::TypedScalarFnInstance;
27+
use vortex_array::scalar_fn::fns::byte_length::ByteLength;
28+
use vortex_array::validity::Validity;
29+
use vortex_session::VortexSession;
30+
31+
// Scalar function execution allocates its output inside the timed region, so use the vendored
32+
// allocator instead of measuring glibc differences between CodSpeed runner images.
33+
#[global_allocator]
34+
static GLOBAL: MiMalloc = MiMalloc;
35+
36+
static SESSION: LazyLock<VortexSession> = LazyLock::new(array_session);
37+
38+
fn main() {
39+
LazyLock::force(&SESSION);
40+
divan::main();
41+
}
42+
43+
const SIZES: &[usize] = &[4_096, 65_536];
44+
45+
fn long_strings(len: usize) -> ArrayRef {
46+
VarBinViewArray::from_iter_str((0..len).map(|i| format!("a string well past inline: {i}")))
47+
.into_array()
48+
}
49+
50+
fn short_strings(len: usize) -> ArrayRef {
51+
VarBinViewArray::from_iter_str((0..len).map(|i| format!("{}", i % 1_000))).into_array()
52+
}
53+
54+
fn bench_byte_length(bencher: Bencher, input: ArrayRef) {
55+
let len = input.len();
56+
bencher
57+
.counter(ItemsCount::new(len))
58+
.with_inputs(|| {
59+
(
60+
ScalarFnArray::try_new(
61+
TypedScalarFnInstance::new(ByteLength, EmptyOptions).erased(),
62+
vec![input.clone()],
63+
)
64+
.unwrap()
65+
.into_array(),
66+
SESSION.create_execution_ctx(),
67+
)
68+
})
69+
.bench_values(|(array, mut ctx)| array.execute::<Canonical>(&mut ctx).unwrap());
70+
}
71+
72+
#[divan::bench(args = SIZES)]
73+
fn inline(bencher: Bencher, len: usize) {
74+
bench_byte_length(bencher, short_strings(len));
75+
}
76+
77+
#[divan::bench(args = SIZES)]
78+
fn out_of_line(bencher: Bencher, len: usize) {
79+
bench_byte_length(bencher, long_strings(len));
80+
}
81+
82+
#[divan::bench(args = SIZES)]
83+
fn nullable_out_of_line(bencher: Bencher, len: usize) {
84+
let validity = Validity::from_iter((0..len).map(|i| i % 8 != 0));
85+
let input = MaskedArray::try_new(long_strings(len), validity)
86+
.unwrap()
87+
.into_array();
88+
bench_byte_length(bencher, input);
89+
}
90+
91+
#[divan::bench(args = SIZES)]
92+
fn nullable_out_of_line_90pct(bencher: Bencher, len: usize) {
93+
let validity = Validity::from_iter((0..len).map(|i| i.is_multiple_of(10)));
94+
let input = MaskedArray::try_new(long_strings(len), validity)
95+
.unwrap()
96+
.into_array();
97+
bench_byte_length(bencher, input);
98+
}

‎vortex-array/benches/like.rs‎

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ use vortex_array::IntoArray;
1313
use vortex_array::VortexSessionExecute;
1414
use vortex_array::arrays::BoolArray;
1515
use vortex_array::arrays::ConstantArray;
16+
use vortex_array::arrays::ScalarFnArray;
1617
use vortex_array::arrays::VarBinViewArray;
17-
use vortex_array::arrays::scalar_fn::ScalarFnFactoryExt;
18+
use vortex_array::scalar_fn::TypedScalarFnInstance;
1819
use vortex_array::scalar_fn::fns::like::Like;
1920
use vortex_array::scalar_fn::fns::like::LikeOptions;
2021

@@ -47,15 +48,15 @@ fn bench_like(bencher: Bencher, pattern: &str, options: LikeOptions) {
4748
bencher
4849
.with_inputs(|| {
4950
(
50-
Like.try_new_array(
51-
ARRAY_SIZE,
52-
options,
53-
[
51+
ScalarFnArray::try_new(
52+
TypedScalarFnInstance::new(Like, options).erased(),
53+
vec![
5454
array.clone(),
5555
ConstantArray::new(pattern, ARRAY_SIZE).into_array(),
5656
],
5757
)
58-
.unwrap(),
58+
.unwrap()
59+
.into_array(),
5960
session.create_execution_ctx(),
6061
)
6162
})
@@ -87,28 +88,50 @@ fn like_regex(bencher: Bencher) {
8788
bench_like(bencher, "h_llo%w%d", LikeOptions::default());
8889
}
8990

90-
#[divan::bench]
91-
fn like_per_row_patterns(bencher: Bencher) {
91+
fn bench_per_row_patterns(bencher: Bencher, patterns: ArrayRef) {
9292
let session = vortex_array::array_session();
9393
let array = strings();
94-
// A non-constant pattern child takes the per-row path; repeated patterns hit the
95-
// compile cache.
96-
let patterns = VarBinViewArray::from_iter_str((0..ARRAY_SIZE).map(|_| "hello%")).into_array();
9794
bencher
9895
.with_inputs(|| {
9996
(
100-
Like.try_new_array(
101-
ARRAY_SIZE,
102-
LikeOptions::default(),
103-
[array.clone(), patterns.clone()],
97+
ScalarFnArray::try_new(
98+
TypedScalarFnInstance::new(Like, LikeOptions::default()).erased(),
99+
vec![array.clone(), patterns.clone()],
104100
)
105-
.unwrap(),
101+
.unwrap()
102+
.into_array(),
106103
session.create_execution_ctx(),
107104
)
108105
})
109106
.bench_values(|(array, mut ctx)| array.execute::<BoolArray>(&mut ctx).unwrap());
110107
}
111108

109+
#[divan::bench]
110+
fn like_per_row_patterns(bencher: Bencher) {
111+
// A non-constant pattern child takes the per-row path; repeated patterns hit the compile
112+
// cache. Its five-byte `%aaa%` shape matches the distinct-pattern fixture below.
113+
let patterns = VarBinViewArray::from_iter_str((0..ARRAY_SIZE).map(|_| "%aaa%")).into_array();
114+
bench_per_row_patterns(bencher, patterns);
115+
}
116+
117+
/// The per-row path with the compile cache defeated: every row carries a distinct pattern of the
118+
/// same shape, so each row pays one pattern compilation.
119+
#[divan::bench]
120+
fn like_per_row_distinct_patterns(bencher: Bencher) {
121+
let patterns = VarBinViewArray::from_iter_str(
122+
(0..ARRAY_SIZE).map(|i| format!("%{}%", distinct_trigram(i))),
123+
)
124+
.into_array();
125+
bench_per_row_patterns(bencher, patterns);
126+
}
127+
128+
/// A distinct three-letter lowercase infix per row, so every pattern has the same shape while
129+
/// no two rows share a compiled pattern.
130+
fn distinct_trigram(i: usize) -> String {
131+
let letter = |shift: usize| char::from(b'a' + u8::try_from((i >> shift) % 26).unwrap());
132+
[letter(0), letter(5), letter(10)].iter().collect()
133+
}
134+
112135
#[divan::bench]
113136
fn ilike_contains(bencher: Bencher) {
114137
bench_like(

‎vortex-geo/Cargo.toml‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ _test-harness = []
3636

3737
[dev-dependencies]
3838
divan = { workspace = true }
39+
mimalloc = { workspace = true }
3940
rstest = { workspace = true }
4041
vortex-array = { workspace = true, features = ["_test-harness"] }
4142
vortex-geo = { path = ".", features = ["_test-harness"] }
@@ -45,5 +46,13 @@ vortex-layout = { workspace = true }
4546
name = "envelope"
4647
harness = false
4748

49+
[[bench]]
50+
name = "binary_predicates"
51+
harness = false
52+
53+
[[bench]]
54+
name = "distance"
55+
harness = false
56+
4857
[lints]
4958
workspace = true

0 commit comments

Comments
 (0)