From dbfa820a7d4a51363cd39ab4d825b22b0be32265 Mon Sep 17 00:00:00 2001 From: Connor Tsui Date: Fri, 30 Oct 2026 12:51:29 -0400 Subject: [PATCH] Establish widening cast benchmark baselines Signed-off-by: Connor Tsui --- vortex-array/benches/cast_primitive.rs | 57 +++++++++++++++++++++- vortex-compute/benches/lane_kernels.rs | 67 ++++++++++++++++++++++++-- 2 files changed, 118 insertions(+), 6 deletions(-) diff --git a/vortex-array/benches/cast_primitive.rs b/vortex-array/benches/cast_primitive.rs index e79946844e1..86e1da99966 100644 --- a/vortex-array/benches/cast_primitive.rs +++ b/vortex-array/benches/cast_primitive.rs @@ -1,6 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors +//! Primitive casts over dense and nullable inputs. +//! +//! Integer widening cases run on the walltime CPU-feature legs to track their generated loops. + #![expect(clippy::unwrap_used)] use std::sync::LazyLock; @@ -35,13 +39,16 @@ fn main() { // to keep the CodSpeed simulation under 1ms per benchmark. const SIZES: &[usize] = &[8_192]; +const WIDEN_SIZES: &[usize] = &[65, 128, 129, 8_192, 16_384]; + static SESSION: LazyLock = LazyLock::new(|| { VortexSession::empty() .with::() .with::() }); -#[divan::bench(args = SIZES)] +#[vortex_bench_support::cpu_features] +#[divan::bench(args = WIDEN_SIZES)] fn cast_u16_to_u32(bencher: Bencher, n: usize) { let mut rng = StdRng::seed_from_u64(42); let arr = PrimitiveArray::from_option_iter((0..n).map(|i| { @@ -62,6 +69,54 @@ fn cast_u16_to_u32(bencher: Bencher, n: usize) { }); } +#[vortex_bench_support::cpu_features] +#[divan::bench(args = WIDEN_SIZES)] +fn cast_u16_to_u32_dense(bencher: Bencher, n: usize) { + let arr = PrimitiveArray::from_iter((0..n).map(|i| u16::try_from(i).unwrap())).into_array(); + + bencher + .with_inputs(|| (arr.clone(), SESSION.create_execution_ctx())) + .bench_refs(|(a, ctx)| { + a.cast(DType::Primitive(PType::U32, Nullability::NonNullable)) + .unwrap() + .execute::(ctx) + }); +} + +#[vortex_bench_support::cpu_features] +#[divan::bench(args = WIDEN_SIZES)] +fn cast_i32_to_i64_dense(bencher: Bencher, n: usize) { + let arr = + PrimitiveArray::from_iter((0..n).map(|i| i32::try_from(i).unwrap() - 8_192)).into_array(); + + bencher + .with_inputs(|| (arr.clone(), SESSION.create_execution_ctx())) + .bench_refs(|(a, ctx)| { + a.cast(DType::Primitive(PType::I64, Nullability::NonNullable)) + .unwrap() + .execute::(ctx) + }); +} + +#[vortex_bench_support::cpu_features] +#[divan::bench(args = WIDEN_SIZES)] +fn cast_i32_to_i64_nullable(bencher: Bencher, n: usize) { + let mut rng = StdRng::seed_from_u64(42); + let arr = PrimitiveArray::from_option_iter((0..n).map(|i| { + rng.random_bool(0.5) + .then_some(i32::try_from(i).unwrap() - 8_192) + })) + .into_array(); + + bencher + .with_inputs(|| (arr.clone(), SESSION.create_execution_ctx())) + .bench_refs(|(a, ctx)| { + a.cast(DType::Primitive(PType::I64, Nullability::Nullable)) + .unwrap() + .execute::(ctx) + }); +} + /// Narrowing fallible cast that goes through `try_map_with_mask`. Inputs are bounded /// so every value fits, isolating the kernel's per-lane checked-cast overhead. #[divan::bench(args = SIZES)] diff --git a/vortex-compute/benches/lane_kernels.rs b/vortex-compute/benches/lane_kernels.rs index ba78c9f6d45..93c374ecfef 100644 --- a/vortex-compute/benches/lane_kernels.rs +++ b/vortex-compute/benches/lane_kernels.rs @@ -9,17 +9,16 @@ //! - propagates valid-lane overflow as `Err`, and //! - suppresses null-lane overflow without the closure ever inspecting `valid`. //! -//! Each Vortex kernel bench has a sibling `arrow_*` baseline bench using the -//! equivalent arrow-rs kernel over the same data shape, so the divan report -//! lines up side-by-side. +//! Arrow baselines cover the corresponding cast and checked-add operations over the same +//! data shape. The unmasked widening cases also compare the two Vortex loop shapes directly. //! //! `lanezip_checked_add_u32` carries `#[cpu_features]`, so it is measured on every //! walltime CPU-feature leg rather than in simulation: it is one lane loop compiled //! differently per leg. Its arrow-rs sibling is compiled only outside CodSpeed. The //! arrow kernel is not Vortex code, so a change in its walltime is never actionable, //! and it flipped by up to 67% between runs of identical code. Run `cargo bench` -//! locally to compare the pair under the same build flags. The cast benches stay in -//! simulation. +//! locally to compare the pair under the same build flags. The paired `map_into` and +//! `map_into_widening` benches also run on the walltime legs. Other cast benches stay in simulation. #![expect(clippy::unwrap_used)] #![expect(clippy::clone_on_ref_ptr)] @@ -66,6 +65,9 @@ fn main() { /// Lanes per cast input; the casts run in simulation. const SIZES: &[usize] = &[16_384]; +/// Widening inputs cover both sides of the tiled and untiled loop boundaries. +const WIDEN_SIZES: &[usize] = &[0, 1, 63, 64, 65, 127, 128, 129, 8_192, 16_384, 16_385]; + /// Lanes per checked-add input; the checked adds run on the walltime legs. /// /// Walltime needs longer iterations than simulation: at 16_384 lanes an iteration took a few @@ -216,6 +218,61 @@ fn map_with_mask_widen_u16_u32(bencher: Bencher, n: usize) { .with_inputs(|| (f.values_u16.clone(), uninit_out::(n))) .bench_values(|(values, mut out)| { values.as_slice().map_into(out.as_mut_slice(), |v| v.as_()); + out + }); +} + +// Both widening variants use the existing map to establish baseline results for the stacked +// optimization before it selects a different loop. Keep their benchmark names stable. +#[vortex_bench_support::cpu_features] +#[divan::bench(args = WIDEN_SIZES)] +fn map_into_i32_i64(bencher: Bencher, n: usize) { + let f = cast_fixture(n); + + bencher + .with_inputs(|| uninit_out::(n)) + .bench_local_values(|mut out| { + f.values_i32.as_slice().map_into(&mut out, Into::into); + out + }); +} + +#[vortex_bench_support::cpu_features] +#[divan::bench(args = WIDEN_SIZES)] +fn map_into_widening_i32_i64(bencher: Bencher, n: usize) { + let f = cast_fixture(n); + + bencher + .with_inputs(|| uninit_out::(n)) + .bench_local_values(|mut out| { + f.values_i32.as_slice().map_into(&mut out, Into::into); + out + }); +} + +#[vortex_bench_support::cpu_features] +#[divan::bench(args = WIDEN_SIZES)] +fn map_into_u16_u32(bencher: Bencher, n: usize) { + let f = cast_fixture(n); + + bencher + .with_inputs(|| uninit_out::(n)) + .bench_local_values(|mut out| { + f.values_u16.as_slice().map_into(&mut out, Into::into); + out + }); +} + +#[vortex_bench_support::cpu_features] +#[divan::bench(args = WIDEN_SIZES)] +fn map_into_widening_u16_u32(bencher: Bencher, n: usize) { + let f = cast_fixture(n); + + bencher + .with_inputs(|| uninit_out::(n)) + .bench_local_values(|mut out| { + f.values_u16.as_slice().map_into(&mut out, Into::into); + out }); }