diff --git a/docs/developer-guide/benchmarking.md b/docs/developer-guide/benchmarking.md index 48df9a2aa21..abe3f109bed 100644 --- a/docs/developer-guide/benchmarking.md +++ b/docs/developer-guide/benchmarking.md @@ -134,6 +134,27 @@ benchmark binary takes. CodSpeed reports exactly that: its performance report on request lists the per-iteration time under `HEAD` for every benchmark the pull request adds or changes, so check any new benchmark there before merging. +### Keep per-iteration work above the harness floor + +The budget has a floor as well as a ceiling. CodSpeed runs each benchmark once and adds a fixed +cost of roughly half a microsecond of reported time around the closure. A closure that does tens +of nanoseconds of real work, such as one small allocation or a fast path that finds nothing to +do, reports mostly that floor, and the floor moves by more than 10% between runs of identical +code. Such benchmarks flag regressions on pull requests that do not touch Rust at all. + +Aim for at least a few microseconds of real work per iteration: + +- When the operation itself is tiny, repeat it a fixed number of times inside the closure and + black-box each result, as `vortex-buffer/benches/allocation.rs` does. +- Drop degenerate inputs, such as a zero-byte allocation or a compaction with nothing to move. +- Size inputs by bytes rather than element count so narrow and wide types land in the same + range, as `vortex-array/benches/filter_fixed_width.rs` does. + +Benchmarks tagged `#[cpu_features]` run on the walltime legs instead, where the floor is timer +resolution and per-iteration jitter. Give those at least tens of microseconds per iteration, and +keep the working set inside the L2 cache of the leg machines (1 MiB on the Graviton leg) when the +benchmark is about kernel code rather than memory bandwidth. + ### Gate CodSpeed-incompatible benchmarks Use `#[cfg(not(codspeed))]` for benchmarks that are incompatible with CodSpeed. diff --git a/encodings/runend/benches/run_end_compress.rs b/encodings/runend/benches/run_end_compress.rs index 6e77dd76efc..c1935024d7a 100644 --- a/encodings/runend/benches/run_end_compress.rs +++ b/encodings/runend/benches/run_end_compress.rs @@ -36,6 +36,9 @@ static SESSION: LazyLock = LazyLock::new(|| { session }); +// (length, run_step). The (4_000, 1024) point is left out: it flipped by 20% between runs of +// identical code on more than 40 pull requests, and (10_000, 1024) and (10_000, 4096) keep long +// runs covered. const BENCH_ARGS: &[(usize, usize)] = &[ (1000, 4), (1000, 16), @@ -43,7 +46,6 @@ const BENCH_ARGS: &[(usize, usize)] = &[ (4_000, 4), (4_000, 16), (4_000, 256), - (4_000, 1024), (10_000, 4), (10_000, 16), (10_000, 256), diff --git a/vortex-array/benches/binary_ops.rs b/vortex-array/benches/binary_ops.rs index a1f14ad9bb1..865dd01fa8c 100644 --- a/vortex-array/benches/binary_ops.rs +++ b/vortex-array/benches/binary_ops.rs @@ -52,21 +52,23 @@ static SESSION: LazyLock = LazyLock::new(array_session); 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. +/// +/// The primitive cases run on the walltime legs, where short iterations are noisy: at 96 KiB per +/// input the `u32` multiply took under 6 µs and flipped by 12% between runs of identical code. +/// 256 KiB per input makes every iteration several times longer while two inputs and the output +/// still fit in a 1 MiB L2 cache. const MIN_PRIMITIVE_LEN: usize = 16_384; -const MIN_PRIMITIVE_INPUT_BYTES: usize = 96 * 1_024; +const MIN_PRIMITIVE_INPUT_BYTES: usize = 256 * 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::(); -/// 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. -const BINARY_SHAPE_CASES: &[(usize, BinaryShape)] = &[ - (128, BinaryShape::PerRowPerRow), - (I64_LEN, BinaryShape::PerRowPerRow), -]; +/// Per-row against per-row at the long length. This is the shape the operators are tuned for, so +/// it is the one every operator is measured on. A 128-row case used to sit alongside it, but at +/// about 2 µs per iteration its walltime moved by up to 95% between runs of identical code. +const BINARY_SHAPE_CASES: &[(usize, BinaryShape)] = &[(I64_LEN, BinaryShape::PerRowPerRow)]; /// Constant operands are measured on `Add` alone, at the long length. /// diff --git a/vortex-array/benches/filter_fixed_width.rs b/vortex-array/benches/filter_fixed_width.rs index eddeb367092..477d09f293d 100644 --- a/vortex-array/benches/filter_fixed_width.rs +++ b/vortex-array/benches/filter_fixed_width.rs @@ -39,9 +39,19 @@ fn main() { static SESSION: LazyLock = LazyLock::new(array_session); -// Keep each case small: the sweep has 24 cases and the targeted sections add only seven more. -// Sized to keep CodSpeed simulation under 1ms per benchmark. -const LEN: usize = 4_096; +/// Bytes of values per input array. +/// +/// Inputs are sized by bytes rather than element count. A fixed element count left the narrow +/// widths far too small: a 4096-element `i8` filter runs in about a microsecond, so CodSpeed +/// reported mostly its fixed per-benchmark cost and the number moved by up to 45% between runs +/// of identical code. 64 KiB keeps every width above that floor, and the widest types, which +/// dominate the simulation time, stay well under the 1 ms budget. +const INPUT_BYTES: usize = 64 * 1024; + +/// Element count of a `T`-wide input. +const fn len_of() -> usize { + INPUT_BYTES / size_of::() +} const DENSITIES: &[f64] = &[0.01, 0.5, 0.8, 0.95]; const CACHED_DENSITIES: &[f64] = &[0.01, 0.1]; @@ -54,10 +64,10 @@ enum Pattern { const PATTERNS: &[Pattern] = &[Pattern::Random, Pattern::Runs, Pattern::Contiguous]; -fn random_mask(density: f64) -> Mask { +fn random_mask(len: usize, density: f64) -> Mask { let threshold = (density * u64::MAX as f64) as u64; let mut state = 0x1234_5678_9abc_def0u64; - Mask::from_buffer(BitBuffer::from_iter((0..LEN).map(|_| { + Mask::from_buffer(BitBuffer::from_iter((0..len).map(|_| { state ^= state << 13; state ^= state >> 7; state ^= state << 17; @@ -65,23 +75,24 @@ fn random_mask(density: f64) -> Mask { }))) } -fn pattern_mask(pattern: Pattern) -> Mask { +fn pattern_mask(len: usize, pattern: Pattern) -> Mask { match pattern { - Pattern::Random => random_mask(0.5), - Pattern::Runs => Mask::from_iter((0..LEN).map(|index| (index / 32).is_multiple_of(2))), - Pattern::Contiguous => Mask::from_slices(LEN, vec![(LEN / 4, LEN * 3 / 4)]), + Pattern::Random => random_mask(len, 0.5), + Pattern::Runs => Mask::from_iter((0..len).map(|index| (index / 32).is_multiple_of(2))), + Pattern::Contiguous => Mask::from_slices(len, vec![(len / 4, len * 3 / 4)]), } } fn bench_filter( bencher: Bencher, array: ArrayRef, - make_mask: impl Fn() -> Mask + Sync, + make_mask: impl Fn(usize) -> Mask + Sync, cache_indices: bool, ) { + let len = array.len(); bencher .with_inputs(|| { - let mask = make_mask(); + let mask = make_mask(len); if cache_indices { let _ = mask.values().unwrap().indices(); } @@ -100,24 +111,24 @@ fn bench_filter( } fn i8_array() -> ArrayRef { - PrimitiveArray::from_iter((0..LEN).map(|index| index as i8)).into_array() + PrimitiveArray::from_iter((0..len_of::()).map(|index| index as i8)).into_array() } fn i16_array() -> ArrayRef { - PrimitiveArray::from_iter((0..LEN).map(|index| index as i16)).into_array() + PrimitiveArray::from_iter((0..len_of::()).map(|index| index as i16)).into_array() } fn i32_array() -> ArrayRef { - PrimitiveArray::from_iter((0..LEN).map(|index| index as i32)).into_array() + PrimitiveArray::from_iter((0..len_of::()).map(|index| index as i32)).into_array() } fn i64_array() -> ArrayRef { - PrimitiveArray::from_iter((0..LEN).map(|index| index as i64)).into_array() + PrimitiveArray::from_iter((0..len_of::()).map(|index| index as i64)).into_array() } fn i128_array() -> ArrayRef { DecimalArray::from_iter( - (0..LEN).map(|index| index as i128), + (0..len_of::()).map(|index| index as i128), DecimalDType::new(19, 0), ) .into_array() @@ -125,7 +136,7 @@ fn i128_array() -> ArrayRef { fn i256_array() -> ArrayRef { DecimalArray::from_iter( - (0..LEN).map(|index| i256::from_i128(index as i128)), + (0..len_of::()).map(|index| i256::from_i128(index as i128)), DecimalDType::new(39, 0), ) .into_array() @@ -135,7 +146,7 @@ macro_rules! random_density_benchmark { ($name:ident, $array:ident) => { #[divan::bench(args = DENSITIES)] fn $name(bencher: Bencher, density: f64) { - bench_filter(bencher, $array(), || random_mask(density), false); + bench_filter(bencher, $array(), |len| random_mask(len, density), false); } }; } @@ -149,15 +160,20 @@ random_density_benchmark!(random_i256, i256_array); #[divan::bench(args = PATTERNS)] fn patterns_i128(bencher: Bencher, pattern: Pattern) { - bench_filter(bencher, i128_array(), || pattern_mask(pattern), false); + bench_filter( + bencher, + i128_array(), + |len| pattern_mask(len, pattern), + false, + ); } #[divan::bench(args = CACHED_DENSITIES)] fn cached_indices_i32(bencher: Bencher, density: f64) { - bench_filter(bencher, i32_array(), || random_mask(density), true); + bench_filter(bencher, i32_array(), |len| random_mask(len, density), true); } #[divan::bench(args = CACHED_DENSITIES)] fn cached_indices_i128(bencher: Bencher, density: f64) { - bench_filter(bencher, i128_array(), || random_mask(density), true); + bench_filter(bencher, i128_array(), |len| random_mask(len, density), true); } diff --git a/vortex-array/benches/take_primitive.rs b/vortex-array/benches/take_primitive.rs index a3787d55a0d..f796980ce5a 100644 --- a/vortex-array/benches/take_primitive.rs +++ b/vortex-array/benches/take_primitive.rs @@ -29,8 +29,9 @@ fn main() { /// Number of indices to take. The top tier is sized to keep CodSpeed simulation under 1ms. const NUM_INDICES: &[usize] = &[1_000, 10_000, 25_000]; -/// Large enough to measure both cache-resident and streaming dictionary decoding. -const GT_NUM_INDICES: &[usize] = &[1_000_000, 16_000_000]; +/// One million codes is the largest input that keeps the walltime legs under the 1 ms budget. A +/// 16 million case ran for 7 ms to 11 ms and swung by up to 60% between runs of identical code. +const GT_NUM_INDICES: &[usize] = &[1_000_000]; /// Size of the source vector / dictionary values. const VECTOR_SIZE: &[usize] = &[16, 256, 2048, 8192]; diff --git a/vortex-array/benches/varbinview_compact.rs b/vortex-array/benches/varbinview_compact.rs index 903bf7a76a4..a3beb74773c 100644 --- a/vortex-array/benches/varbinview_compact.rs +++ b/vortex-array/benches/varbinview_compact.rs @@ -29,11 +29,12 @@ fn main() { const ARGS: &[(usize, usize)] = &[ // (output_size, buffer_utilization_pct) - // Output sizes sized to keep CodSpeed simulation under 1ms per benchmark. + // Output sizes sized to keep CodSpeed simulation under 1ms per benchmark. Only low + // utilization is measured: at 90% there is nothing to compact, so the timed region was a + // single check that CodSpeed reported as harness overhead, moving by 20% between runs of + // identical code. (1 << 10, 10), - (1 << 10, 90), (1 << 11, 10), - (1 << 11, 90), ]; static SESSION: LazyLock = LazyLock::new(array_session); diff --git a/vortex-buffer/benches/allocation.rs b/vortex-buffer/benches/allocation.rs index 6cd923b5dfb..7f299ab91f1 100644 --- a/vortex-buffer/benches/allocation.rs +++ b/vortex-buffer/benches/allocation.rs @@ -1,6 +1,13 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors +//! Allocate-and-drop cost of Vortex buffers next to their `bytes` and Arrow equivalents. +//! +//! A single allocate-and-drop pair takes tens of nanoseconds. CodSpeed's simulation runs each +//! benchmark once and adds a fixed harness cost of roughly half a microsecond of reported time, +//! so a pair on its own measured that cost and moved by more than 10% on pull requests that did +//! not touch Rust code. Every iteration therefore repeats the operation [`BATCH`] times. + use allocator_api2::alloc::Global; use arrow_buffer::MutableBuffer; use bytes::BytesMut; @@ -10,97 +17,128 @@ use vortex_buffer::Buffer; use vortex_buffer::BufferAllocatorRef; use vortex_buffer::BufferMut; -const SIZES: &[usize] = &[0, 64, 256, 1024, 16_384, 65_536]; +/// Sizes start at 64 bytes: none of these types allocate for a zero-byte request. +const SIZES: &[usize] = &[64, 256, 1024, 16_384, 65_536]; + +/// Allocate-and-drop pairs per timed iteration. +const BATCH: usize = 256; fn main() { divan::main(); } +/// Allocates and drops [`BATCH`] values, black-boxing each one so the pair cannot be elided. +fn allocate_drop_batch(mut allocate: impl FnMut() -> T) { + for _ in 0..BATCH { + drop(divan::black_box(allocate())); + } +} + +/// One batch of zero-filled vectors, built outside the timed region. +/// +/// Converting a `Vec` copies it when the allocation is not aligned for the buffer, so the batch +/// shrinks with the size to keep each iteration at about a mebibyte of copying. +fn vec_batch(size: usize) -> Vec> { + let len = ((1 << 20) / size).clamp(16, BATCH); + (0..len).map(|_| vec![0u8; size]).collect() +} + #[divan::bench(args = SIZES)] fn allocate_drop_vortex(bencher: Bencher, size: usize) { - bencher.bench(|| drop(BufferMut::::with_capacity(size))); + bencher.bench(|| allocate_drop_batch(|| BufferMut::::with_capacity(size))); } #[divan::bench(args = SIZES)] fn allocate_drop_vortex_custom(bencher: Bencher, size: usize) { bencher .with_inputs(|| BufferAllocatorRef::new(Global)) - .bench_refs(|allocator| drop(allocator.with_capacity::(size))); + .bench_refs(|allocator| allocate_drop_batch(|| allocator.with_capacity::(size))); } #[divan::bench(args = SIZES)] fn allocate_drop_vortex_minimal_alignment(bencher: Bencher, size: usize) { bencher.bench(|| { - drop(BufferMut::::with_capacity_preferred_aligned( - size, - Alignment::of::(), - None, - )) + allocate_drop_batch(|| { + BufferMut::::with_capacity_preferred_aligned(size, Alignment::of::(), None) + }) }); } #[divan::bench(args = SIZES)] fn allocate_drop_bytes(bencher: Bencher, size: usize) { - bencher.bench(|| drop(BytesMut::with_capacity(size))); + bencher.bench(|| allocate_drop_batch(|| BytesMut::with_capacity(size))); } #[divan::bench(args = SIZES)] fn allocate_drop_arrow(bencher: Bencher, size: usize) { - bencher.bench(|| drop(MutableBuffer::with_capacity(size))); + bencher.bench(|| allocate_drop_batch(|| MutableBuffer::with_capacity(size))); } #[divan::bench(args = SIZES)] fn allocate_freeze_drop_vortex(bencher: Bencher, size: usize) { - bencher.bench(|| drop(BufferMut::::with_capacity(size).freeze())); + bencher.bench(|| allocate_drop_batch(|| BufferMut::::with_capacity(size).freeze())); } #[divan::bench(args = SIZES)] fn allocate_freeze_drop_vortex_custom(bencher: Bencher, size: usize) { bencher .with_inputs(|| BufferAllocatorRef::new(Global)) - .bench_refs(|allocator| drop(allocator.with_capacity::(size).freeze())); + .bench_refs(|allocator| { + allocate_drop_batch(|| allocator.with_capacity::(size).freeze()) + }); } #[divan::bench(args = SIZES)] fn allocate_freeze_drop_vortex_minimal_alignment(bencher: Bencher, size: usize) { bencher.bench(|| { - drop( + allocate_drop_batch(|| { BufferMut::::with_capacity_preferred_aligned(size, Alignment::of::(), None) - .freeze(), - ) + .freeze() + }) }); } #[divan::bench(args = SIZES)] fn allocate_freeze_drop_bytes(bencher: Bencher, size: usize) { - bencher.bench(|| drop(BytesMut::with_capacity(size).freeze())); + bencher.bench(|| allocate_drop_batch(|| BytesMut::with_capacity(size).freeze())); } #[divan::bench(args = SIZES)] fn allocate_freeze_drop_arrow(bencher: Bencher, size: usize) { bencher.bench(|| { - let buffer: arrow_buffer::Buffer = MutableBuffer::with_capacity(size).into(); - drop(buffer) + allocate_drop_batch(|| arrow_buffer::Buffer::from(MutableBuffer::with_capacity(size))) }); } #[divan::bench(args = SIZES)] fn from_vec_drop_vortex(bencher: Bencher, size: usize) { bencher - .with_inputs(|| vec![0u8; size]) - .bench_values(|values| drop(Buffer::from(values))); + .with_inputs(|| vec_batch(size)) + .bench_values(|vecs| { + for values in vecs { + drop(divan::black_box(Buffer::from(values))); + } + }); } #[divan::bench(args = SIZES)] fn from_vec_drop_bytes(bencher: Bencher, size: usize) { bencher - .with_inputs(|| vec![0u8; size]) - .bench_values(|values| drop(bytes::Bytes::from(values))); + .with_inputs(|| vec_batch(size)) + .bench_values(|vecs| { + for values in vecs { + drop(divan::black_box(bytes::Bytes::from(values))); + } + }); } #[divan::bench(args = SIZES)] fn from_vec_drop_arrow(bencher: Bencher, size: usize) { bencher - .with_inputs(|| vec![0u8; size]) - .bench_values(|values| drop(arrow_buffer::Buffer::from_vec(values))); + .with_inputs(|| vec_batch(size)) + .bench_values(|vecs| { + for values in vecs { + drop(divan::black_box(arrow_buffer::Buffer::from_vec(values))); + } + }); } diff --git a/vortex-buffer/benches/collect_bool.rs b/vortex-buffer/benches/collect_bool.rs index 864179471f8..f5fc14f3518 100644 --- a/vortex-buffer/benches/collect_bool.rs +++ b/vortex-buffer/benches/collect_bool.rs @@ -46,6 +46,14 @@ fn main() { const INPUT_SIZE: &[usize] = &[1024, 65_536]; +/// Sizes for the `words_gather_*` benchmarks. +/// +/// The tagged pair is measured on the walltime legs, where a 1024-bool gather ran in tens of +/// nanoseconds, within timer resolution, and its reported time moved by 2x between runs of +/// identical code. A million bools keeps each iteration in the tens to hundreds of microseconds +/// and the scalar loop still well under the 1 ms budget. +const GATHER_INPUT_SIZE: &[usize] = &[65_536, 1_048_576]; + /// Deterministic pseudo-random words (LCG), the source for all benchmark inputs. fn make_words(len: usize) -> impl Iterator { let mut state = 0x9E37_79B9_7F4A_7C15u64; @@ -107,7 +115,7 @@ fn bench_words_gather( } #[vortex_bench_support::cpu_features] -#[divan::bench(args = INPUT_SIZE)] +#[divan::bench(args = GATHER_INPUT_SIZE)] fn words_gather_dispatch(bencher: Bencher, len: usize) { bench_words_gather(bencher, len, |words, len, bools| { // SAFETY: `collect_bool_words` invokes the predicate with indices `0..len` only. @@ -116,7 +124,7 @@ fn words_gather_dispatch(bencher: Bencher, len: usize) { } #[vortex_bench_support::cpu_features] -#[divan::bench(args = INPUT_SIZE)] +#[divan::bench(args = GATHER_INPUT_SIZE)] fn words_gather_scalar(bencher: Bencher, len: usize) { bench_words_gather(bencher, len, |words, len, bools| { // SAFETY: `collect_bool_words_old` invokes the predicate with indices `0..len` only. @@ -126,7 +134,7 @@ fn words_gather_scalar(bencher: Bencher, len: usize) { #[cfg(target_arch = "x86_64")] #[cfg(not(codspeed))] -#[divan::bench(args = INPUT_SIZE)] +#[divan::bench(args = GATHER_INPUT_SIZE)] fn words_gather_sse2(bencher: Bencher, len: usize) { bench_words_gather(bencher, len, |words, len, bools| { // SAFETY: SSE2 is part of the x86-64 baseline; indices passed are `0..len`. @@ -136,7 +144,7 @@ fn words_gather_sse2(bencher: Bencher, len: usize) { #[cfg(target_arch = "x86_64")] #[cfg(not(codspeed))] -#[divan::bench(args = INPUT_SIZE)] +#[divan::bench(args = GATHER_INPUT_SIZE)] fn words_gather_avx2(bencher: Bencher, len: usize) { if !is_x86_feature_detected!("avx2") { return; @@ -149,7 +157,7 @@ fn words_gather_avx2(bencher: Bencher, len: usize) { #[cfg(target_arch = "x86_64")] #[cfg(not(codspeed))] -#[divan::bench(args = INPUT_SIZE)] +#[divan::bench(args = GATHER_INPUT_SIZE)] fn words_gather_avx512(bencher: Bencher, len: usize) { if !(is_x86_feature_detected!("avx512f") && is_x86_feature_detected!("avx512bw")) { return; @@ -162,7 +170,7 @@ fn words_gather_avx512(bencher: Bencher, len: usize) { #[cfg(target_arch = "aarch64")] #[cfg(not(codspeed))] -#[divan::bench(args = INPUT_SIZE)] +#[divan::bench(args = GATHER_INPUT_SIZE)] fn words_gather_neon(bencher: Bencher, len: usize) { bench_words_gather(bencher, len, |words, len, bools| { // SAFETY: NEON is part of the aarch64 baseline; indices passed are `0..len`. diff --git a/vortex-compute/benches/lane_kernels.rs b/vortex-compute/benches/lane_kernels.rs index 2e9e145add7..281646ada67 100644 --- a/vortex-compute/benches/lane_kernels.rs +++ b/vortex-compute/benches/lane_kernels.rs @@ -54,8 +54,17 @@ fn main() { divan::main(); } +/// Lanes per cast input; the casts run in simulation. const SIZES: &[usize] = &[16_384]; +/// 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 +/// microseconds and its reported time flipped by up to 25% between runs of identical code. +/// 65_536 lanes make each iteration several times longer while two inputs, two masks, and the +/// output still fit in a 1 MiB L2 cache. +const ADD_SIZES: &[usize] = &[65_536]; + // ----------------------------------------------------------------------------- // Cast fixture (u64/u16/i32 lanes + a single validity mask). // ----------------------------------------------------------------------------- @@ -330,7 +339,7 @@ fn add_fixture(n: usize) -> AddFixture { } #[vortex_bench_support::cpu_features] -#[divan::bench(args = SIZES)] +#[divan::bench(args = ADD_SIZES)] fn lanezip_checked_add_u32(bencher: Bencher, n: usize) { let f = add_fixture(n); bencher @@ -353,7 +362,7 @@ fn lanezip_checked_add_u32(bencher: Bencher, n: usize) { } #[vortex_bench_support::cpu_features] -#[divan::bench(args = SIZES)] +#[divan::bench(args = ADD_SIZES)] fn arrow_checked_add_u32(bencher: Bencher, n: usize) { let f = add_fixture(n); let lhs_arr: ArrowArrayRef = Arc::new(UInt32Array::new( diff --git a/vortex-layout/benches/zone_map_prune.rs b/vortex-layout/benches/zone_map_prune.rs index 87eb21fa9e4..9ea491f9647 100644 --- a/vortex-layout/benches/zone_map_prune.rs +++ b/vortex-layout/benches/zone_map_prune.rs @@ -303,11 +303,13 @@ fn is_not_null_pred(bencher: Bencher, num_zones: usize) { ); } -/// A 16-term `OR` chain, which is where lowering cost grows relative to evaluation cost. +/// A 4-term `OR` chain, which is where lowering cost grows relative to evaluation cost. Sixteen +/// terms ran for 1.4 ms in the CodSpeed simulation at 1024 zones, over the 1 ms budget, and +/// longer still at 8192 zones. #[divan::bench(args = ZONE_COUNTS)] fn or_chain(bencher: Bencher, num_zones: usize) { static PREDICATE: LazyLock = LazyLock::new(|| { - let expr = (0..16i32) + let expr = (0..4i32) .map(|i| eq(root(), lit(i * 500))) .reduce(or) .unwrap();