From 931b4b16329df6c788d3403f43f3219035f0c064 Mon Sep 17 00:00:00 2001 From: Connor Tsui Date: Wed, 2 Sep 2026 10:32:59 -0400 Subject: [PATCH] Benchmark scalar functions on production-sized inputs Signed-off-by: Connor Tsui --- vortex-array/benches/binary_ops.rs | 31 ++++++------------ vortex-array/benches/compare.rs | 49 ++++++++++++++++++----------- vortex-array/benches/support/mod.rs | 25 +++++++++++++++ 3 files changed, 66 insertions(+), 39 deletions(-) create mode 100644 vortex-array/benches/support/mod.rs diff --git a/vortex-array/benches/binary_ops.rs b/vortex-array/benches/binary_ops.rs index a1f14ad9bb1..5867e9f21ce 100644 --- a/vortex-array/benches/binary_ops.rs +++ b/vortex-array/benches/binary_ops.rs @@ -18,7 +18,6 @@ reason = "benchmark fixtures use indices that fit in the chosen widths" )] -use std::mem::size_of; use std::sync::LazyLock; use divan::Bencher; @@ -38,6 +37,10 @@ use vortex_array::dtype::DecimalDType; use vortex_array::scalar_fn::fns::operators::Operator; use vortex_session::VortexSession; +mod support; + +use support::fixed_width_array_len; + #[global_allocator] static GLOBAL: MiMalloc = MiMalloc; @@ -51,15 +54,10 @@ static SESSION: LazyLock = LazyLock::new(array_session); /// Sized to keep CodSpeed simulation under 1ms per benchmark. const LEN: usize = 4_096; -/// Primitive cases process at least this many rows and this many value bytes per varying input. -/// This lengthens narrow integer cases while keeping the current CodSpeed simulations below 1 ms. -const MIN_PRIMITIVE_LEN: usize = 16_384; -const MIN_PRIMITIVE_INPUT_BYTES: usize = 96 * 1_024; - -const I8_LEN: usize = primitive_len::(); -const I16_LEN: usize = primitive_len::(); -const I32_LEN: usize = primitive_len::(); -const I64_LEN: usize = primitive_len::(); +const I8_LEN: usize = fixed_width_array_len::(); +const I16_LEN: usize = fixed_width_array_len::(); +const I32_LEN: usize = fixed_width_array_len::(); +const I64_LEN: usize = fixed_width_array_len::(); /// Per-row against per-row, short and long. This is the shape the operators are tuned for, so it /// is the one every operator is measured on. @@ -317,8 +315,8 @@ fn div_decimal_i128_nullable(bencher: Bencher) { #[vortex_bench_support::cpu_features] #[divan::bench] fn lt_i64_nullable(bencher: Bencher) { - let lhs = primitive_nullable(0, 7, LEN).into_array(); - let rhs = primitive_nullable(1_000_000, 5, LEN).into_array(); + let lhs = primitive_nullable(0, 7, I64_LEN).into_array(); + let rhs = primitive_nullable(1_000_000, 5, I64_LEN).into_array(); bench_bool(bencher, lhs, rhs, Operator::Lt); } @@ -361,15 +359,6 @@ fn bench_binary( }); } -const fn primitive_len() -> usize { - let input_len = MIN_PRIMITIVE_INPUT_BYTES / size_of::(); - if input_len > MIN_PRIMITIVE_LEN { - input_len - } else { - MIN_PRIMITIVE_LEN - } -} - fn primitive_nonnull(base: i64, len: usize) -> PrimitiveArray { PrimitiveArray::from_iter((0..len as i64).map(|i| base + i)) } diff --git a/vortex-array/benches/compare.rs b/vortex-array/benches/compare.rs index 8e040ce1b91..b02717846e5 100644 --- a/vortex-array/benches/compare.rs +++ b/vortex-array/benches/compare.rs @@ -46,6 +46,10 @@ use vortex_array::scalar_fn::fns::operators::Operator; use vortex_array::validity::Validity; use vortex_buffer::Buffer; +mod support; + +use support::fixed_width_array_len; + #[global_allocator] static GLOBAL: MiMalloc = MiMalloc; @@ -53,9 +57,15 @@ fn main() { divan::main(); } -// Sized to keep CodSpeed simulation under 1ms per benchmark. +// Sized to keep the more expensive CodSpeed simulations under 1ms per benchmark. const ARRAY_SIZE: usize = 8_192; +const F32_ARRAY_SIZE: usize = fixed_width_array_len::(); +const F64_ARRAY_SIZE: usize = fixed_width_array_len::(); +const I64_ARRAY_SIZE: usize = fixed_width_array_len::(); +const U8_ARRAY_SIZE: usize = fixed_width_array_len::(); +const U64_ARRAY_SIZE: usize = fixed_width_array_len::(); + fn bench_compare(bencher: Bencher, lhs: ArrayRef, rhs: ArrayRef, op: Operator) { let session = vortex_array::array_session(); let len = lhs.len(); @@ -85,9 +95,9 @@ fn bool_array_nullable(rng: &mut StdRng) -> ArrayRef { .into_array() } -fn int_array(rng: &mut StdRng) -> ArrayRef { +fn int_array(rng: &mut StdRng, len: usize) -> ArrayRef { let range = Uniform::new(0i64, 100_000_000).unwrap(); - (0..ARRAY_SIZE) + (0..len) .map(|_| rng.sample(range)) .collect::>() .into_array() @@ -96,37 +106,37 @@ fn int_array(rng: &mut StdRng) -> ArrayRef { fn int_array_nullable(rng: &mut StdRng) -> ArrayRef { let range = Uniform::new(0i64, 100_000_000).unwrap(); PrimitiveArray::new( - (0..ARRAY_SIZE) + (0..I64_ARRAY_SIZE) .map(|_| rng.sample(range)) .collect::>(), - Validity::from_iter((0..ARRAY_SIZE).map(|_| rng.random_bool(0.9))), + Validity::from_iter((0..I64_ARRAY_SIZE).map(|_| rng.random_bool(0.9))), ) .into_array() } fn float_array(rng: &mut StdRng) -> ArrayRef { - (0..ARRAY_SIZE) + (0..F64_ARRAY_SIZE) .map(|_| rng.random_range(0.0f64..1.0)) .collect::>() .into_array() } fn u8_array(rng: &mut StdRng) -> ArrayRef { - (0..ARRAY_SIZE) + (0..U8_ARRAY_SIZE) .map(|_| rng.random::()) .collect::>() .into_array() } fn u64_array(rng: &mut StdRng) -> ArrayRef { - (0..ARRAY_SIZE) + (0..U64_ARRAY_SIZE) .map(|_| rng.random::()) .collect::>() .into_array() } fn f32_array(rng: &mut StdRng) -> ArrayRef { - (0..ARRAY_SIZE) + (0..F32_ARRAY_SIZE) .map(|_| rng.random_range(0.0f32..1.0)) .collect::>() .into_array() @@ -178,8 +188,8 @@ fn compare_bool_constant(bencher: Bencher) { #[divan::bench] fn compare_int(bencher: Bencher) { let mut rng = StdRng::seed_from_u64(0); - let arr1 = int_array(&mut rng); - let arr2 = int_array(&mut rng); + let arr1 = int_array(&mut rng, I64_ARRAY_SIZE); + let arr2 = int_array(&mut rng, I64_ARRAY_SIZE); bench_compare(bencher, arr1, arr2, Operator::Gte); } @@ -196,8 +206,8 @@ fn compare_int_nullable(bencher: Bencher) { #[divan::bench] fn compare_int_constant(bencher: Bencher) { let mut rng = StdRng::seed_from_u64(0); - let arr = int_array(&mut rng); - let constant = ConstantArray::new(50_000_000i64, ARRAY_SIZE).into_array(); + let arr = int_array(&mut rng, I64_ARRAY_SIZE); + let constant = ConstantArray::new(50_000_000i64, I64_ARRAY_SIZE).into_array(); bench_compare(bencher, arr, constant, Operator::Gte); } @@ -232,8 +242,8 @@ fn compare_f32(bencher: Bencher) { #[divan::bench] fn compare_int_eq(bencher: Bencher) { let mut rng = StdRng::seed_from_u64(0); - let arr1 = int_array(&mut rng); - let arr2 = int_array(&mut rng); + let arr1 = int_array(&mut rng, I64_ARRAY_SIZE); + let arr2 = int_array(&mut rng, I64_ARRAY_SIZE); bench_compare(bencher, arr1, arr2, Operator::Eq); } @@ -279,9 +289,12 @@ fn compare_string_constant(bencher: Bencher) { } fn struct_array(rng: &mut StdRng) -> ArrayRef { - StructArray::from_fields(&[("a", int_array(rng)), ("b", int_array(rng))]) - .unwrap() - .into_array() + StructArray::from_fields(&[ + ("a", int_array(rng, ARRAY_SIZE)), + ("b", int_array(rng, ARRAY_SIZE)), + ]) + .unwrap() + .into_array() } #[divan::bench] diff --git a/vortex-array/benches/support/mod.rs b/vortex-array/benches/support/mod.rs new file mode 100644 index 00000000000..e07c4757516 --- /dev/null +++ b/vortex-array/benches/support/mod.rs @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +//! Shared sizing policy for array benchmarks. + +use std::mem::size_of; + +const DEFAULT_DATA_BLOCK_BYTES: usize = 1 << 20; +const DEFAULT_SCAN_SPLIT_ROWS: usize = 100_000; + +/// Returns a production-sized row count for one fixed-width input array. +/// +/// The sizing policy mirrors Vortex's default [1 MiB writer block] and [100,000-row scan split]. +/// The smaller limit models the amount of one column processed by a scan task. +/// +/// [1 MiB writer block]: https://github.com/vortex-data/vortex/blob/aaed723dffe1d2c54fcc7f1bbcf760726d4e8056/vortex-file/src/strategy.rs#L67-L75 +/// [100,000-row scan split]: https://github.com/vortex-data/vortex/blob/aaed723dffe1d2c54fcc7f1bbcf760726d4e8056/vortex-layout/src/scan/mod.rs#L16-L19 +pub const fn fixed_width_array_len() -> usize { + let data_block_rows = DEFAULT_DATA_BLOCK_BYTES / size_of::(); + if data_block_rows < DEFAULT_SCAN_SPLIT_ROWS { + data_block_rows + } else { + DEFAULT_SCAN_SPLIT_ROWS + } +}