Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/codspeed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ jobs:
- { shard: 6, name: "Encodings 3", packages: "vortex-pco vortex-runend vortex-sequence" }
- { shard: 7, name: "Encodings 4", packages: "vortex-sparse vortex-zigzag vortex-zstd" }
- { shard: 8, name: "Storage formats & row encoding", packages: "vortex-flatbuffers vortex-proto vortex-btrblocks vortex-row" }
- { shard: 9, name: "Tensor & geo", packages: "vortex-tensor vortex-geo" }
name: "Benchmark with Codspeed (Shard #${{ matrix.shard }})"
timeout-minutes: 30
runs-on: >-
Expand Down
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions vortex-array/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ harness = false
name = "like"
harness = false

[[bench]]
name = "byte_length"
harness = false

[[bench]]
name = "interleave"
harness = false
Expand Down
24 changes: 24 additions & 0 deletions vortex-array/benches/binary_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,30 @@ fn add_i64_nullable(bencher: Bencher) {
bench_primitive(bencher, lhs, rhs, Operator::Add);
}

#[divan::bench]
fn add_i64_constant(bencher: Bencher) {
let lhs = primitive_nonnull(0).into_array();
let rhs = ConstantArray::new(1_000_000i64, LEN).into_array();

bench_primitive(bencher, lhs, rhs, Operator::Add);
}

#[divan::bench]
fn add_i32_nonnull(bencher: Bencher) {
let lhs = primitive_i32_small_nonnull(1).into_array();
let rhs = primitive_i32_small_nonnull(17).into_array();

bench_primitive(bencher, lhs, rhs, Operator::Add);
}

#[divan::bench]
fn add_u32_nonnull(bencher: Bencher) {
let lhs = primitive_u32_small_nonnull(1).into_array();
let rhs = primitive_u32_small_nonnull(17).into_array();

bench_primitive(bencher, lhs, rhs, Operator::Add);
}

#[divan::bench]
fn mul_i64_nonnull(bencher: Bencher) {
let lhs = primitive_small_nonnull(1).into_array();
Expand Down
98 changes: 98 additions & 0 deletions vortex-array/benches/byte_length.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright the Vortex contributors

//! Baseline throughput for `byte_length` over UTF-8 view arrays.
//!
//! The arms cover inline and out-of-line views, plus nullable out-of-line views. Their names are
//! intended to remain stable across scalar-function implementation changes so CodSpeed can compare
//! them against `develop`.

#![expect(clippy::unwrap_used)]

use std::sync::LazyLock;

use divan::Bencher;
use divan::counter::ItemsCount;
use mimalloc::MiMalloc;
use vortex_array::ArrayRef;
use vortex_array::Canonical;
use vortex_array::IntoArray;
use vortex_array::VortexSessionExecute;
use vortex_array::array_session;
use vortex_array::arrays::MaskedArray;
use vortex_array::arrays::ScalarFnArray;
use vortex_array::arrays::VarBinViewArray;
use vortex_array::scalar_fn::EmptyOptions;
use vortex_array::scalar_fn::TypedScalarFnInstance;
use vortex_array::scalar_fn::fns::byte_length::ByteLength;
use vortex_array::validity::Validity;
use vortex_session::VortexSession;

// Scalar function execution allocates its output inside the timed region, so use the vendored
// allocator instead of measuring glibc differences between CodSpeed runner images.
#[global_allocator]
static GLOBAL: MiMalloc = MiMalloc;

static SESSION: LazyLock<VortexSession> = LazyLock::new(array_session);

fn main() {
LazyLock::force(&SESSION);
divan::main();
}

const SIZES: &[usize] = &[4_096, 65_536];

fn long_strings(len: usize) -> ArrayRef {
VarBinViewArray::from_iter_str((0..len).map(|i| format!("a string well past inline: {i}")))
.into_array()
}

fn short_strings(len: usize) -> ArrayRef {
VarBinViewArray::from_iter_str((0..len).map(|i| format!("{}", i % 1_000))).into_array()
}

fn bench_byte_length(bencher: Bencher, input: ArrayRef) {
let len = input.len();
bencher
.counter(ItemsCount::new(len))
.with_inputs(|| {
(
ScalarFnArray::try_new(
TypedScalarFnInstance::new(ByteLength, EmptyOptions).erased(),
vec![input.clone()],
)
.unwrap()
.into_array(),
SESSION.create_execution_ctx(),
)
})
.bench_values(|(array, mut ctx)| array.execute::<Canonical>(&mut ctx).unwrap());
}

#[divan::bench(args = SIZES)]
fn inline(bencher: Bencher, len: usize) {
bench_byte_length(bencher, short_strings(len));
}

#[divan::bench(args = SIZES)]
fn out_of_line(bencher: Bencher, len: usize) {
bench_byte_length(bencher, long_strings(len));
}

#[divan::bench(args = SIZES)]
fn nullable_out_of_line(bencher: Bencher, len: usize) {
let validity = Validity::from_iter((0..len).map(|i| i % 8 != 0));
let input = MaskedArray::try_new(long_strings(len), validity)
.unwrap()
.into_array();
bench_byte_length(bencher, input);
}

#[divan::bench(args = SIZES)]
fn nullable_out_of_line_90pct(bencher: Bencher, len: usize) {
let validity = Validity::from_iter((0..len).map(|i| i.is_multiple_of(10)));
let input = MaskedArray::try_new(long_strings(len), validity)
.unwrap()
.into_array();
bench_byte_length(bencher, input);
}
55 changes: 39 additions & 16 deletions vortex-array/benches/like.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ use vortex_array::IntoArray;
use vortex_array::VortexSessionExecute;
use vortex_array::arrays::BoolArray;
use vortex_array::arrays::ConstantArray;
use vortex_array::arrays::ScalarFnArray;
use vortex_array::arrays::VarBinViewArray;
use vortex_array::arrays::scalar_fn::ScalarFnFactoryExt;
use vortex_array::scalar_fn::TypedScalarFnInstance;
use vortex_array::scalar_fn::fns::like::Like;
use vortex_array::scalar_fn::fns::like::LikeOptions;

Expand Down Expand Up @@ -47,15 +48,15 @@ fn bench_like(bencher: Bencher, pattern: &str, options: LikeOptions) {
bencher
.with_inputs(|| {
(
Like.try_new_array(
ARRAY_SIZE,
options,
[
ScalarFnArray::try_new(
TypedScalarFnInstance::new(Like, options).erased(),
vec![
array.clone(),
ConstantArray::new(pattern, ARRAY_SIZE).into_array(),
],
)
.unwrap(),
.unwrap()
.into_array(),
session.create_execution_ctx(),
)
})
Expand Down Expand Up @@ -87,28 +88,50 @@ fn like_regex(bencher: Bencher) {
bench_like(bencher, "h_llo%w%d", LikeOptions::default());
}

#[divan::bench]
fn like_per_row_patterns(bencher: Bencher) {
fn bench_per_row_patterns(bencher: Bencher, patterns: ArrayRef) {
let session = vortex_array::array_session();
let array = strings();
// A non-constant pattern child takes the per-row path; repeated patterns hit the
// compile cache.
let patterns = VarBinViewArray::from_iter_str((0..ARRAY_SIZE).map(|_| "hello%")).into_array();
bencher
.with_inputs(|| {
(
Like.try_new_array(
ARRAY_SIZE,
LikeOptions::default(),
[array.clone(), patterns.clone()],
ScalarFnArray::try_new(
TypedScalarFnInstance::new(Like, LikeOptions::default()).erased(),
vec![array.clone(), patterns.clone()],
)
.unwrap(),
.unwrap()
.into_array(),
session.create_execution_ctx(),
)
})
.bench_values(|(array, mut ctx)| array.execute::<BoolArray>(&mut ctx).unwrap());
}

#[divan::bench]
fn like_per_row_patterns(bencher: Bencher) {
// A non-constant pattern child takes the per-row path; repeated patterns hit the compile
// cache. Its five-byte `%aaa%` shape matches the distinct-pattern fixture below.
let patterns = VarBinViewArray::from_iter_str((0..ARRAY_SIZE).map(|_| "%aaa%")).into_array();
bench_per_row_patterns(bencher, patterns);
}

/// The per-row path with the compile cache defeated: every row carries a distinct pattern of the
/// same shape, so each row pays one pattern compilation.
#[divan::bench]
fn like_per_row_distinct_patterns(bencher: Bencher) {
let patterns = VarBinViewArray::from_iter_str(
(0..ARRAY_SIZE).map(|i| format!("%{}%", distinct_trigram(i))),
)
.into_array();
bench_per_row_patterns(bencher, patterns);
}

/// A distinct three-letter lowercase infix per row, so every pattern has the same shape while
/// no two rows share a compiled pattern.
fn distinct_trigram(i: usize) -> String {
let letter = |shift: usize| char::from(b'a' + u8::try_from((i >> shift) % 26).unwrap());
[letter(0), letter(5), letter(10)].iter().collect()
}

#[divan::bench]
fn ilike_contains(bencher: Bencher) {
bench_like(
Expand Down
9 changes: 9 additions & 0 deletions vortex-geo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ _test-harness = []

[dev-dependencies]
divan = { workspace = true }
mimalloc = { workspace = true }
rstest = { workspace = true }
vortex-array = { workspace = true, features = ["_test-harness"] }
vortex-geo = { path = ".", features = ["_test-harness"] }
Expand All @@ -45,5 +46,13 @@ vortex-layout = { workspace = true }
name = "envelope"
harness = false

[[bench]]
name = "binary_predicates"
harness = false

[[bench]]
name = "distance"
harness = false

[lints]
workspace = true
Loading