diff --git a/Cargo.lock b/Cargo.lock index efd8fcf1a69..2f0171d8a02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11697,11 +11697,6 @@ dependencies = [ name = "vortex-compute" version = "0.1.0" dependencies = [ - "arrow-arith 59.3.2", - "arrow-array 59.3.2", - "arrow-buffer 59.3.2", - "arrow-cast 59.3.2", - "arrow-schema 59.3.2", "codspeed-divan-compat", "mimalloc", "num-traits", @@ -12409,7 +12404,6 @@ version = "0.1.0" dependencies = [ "arrow-array 59.3.2", "arrow-row 59.3.2", - "arrow-schema 59.3.2", "codspeed-divan-compat", "mimalloc", "num-traits", diff --git a/encodings/fastlanes/benches/bitpack_compare.rs b/encodings/fastlanes/benches/bitpack_compare.rs index 200916c0e7c..9cfc5cb5201 100644 --- a/encodings/fastlanes/benches/bitpack_compare.rs +++ b/encodings/fastlanes/benches/bitpack_compare.rs @@ -1,9 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -//! Compare an already-packed `BitPackedArray` against a constant value. Compares the -//! out-of-range fast path (constant outside `[0, 2^bit_width - 1]`) against an explicit -//! "decompress, then compare" baseline. +//! Compare an already-packed `BitPackedArray` against a constant outside +//! `[0, 2^bit_width - 1]`, which the bit width alone decides without unpacking. //! //! Sized to finish quickly. Run with `cargo bench -p vortex-fastlanes --bench bitpack_compare`. @@ -115,22 +114,6 @@ fn fast_eq_out_of_range(bencher: Bencher) { }); } -#[divan::bench] -fn baseline_eq(bencher: Bencher) { - let (array, rhs, mut ctx) = build_inputs(); - bencher.bench_local(|| { - // What the fallback would do: materialize the unpacked primitive, then run Arrow - // compare on it. - let primitive = array.clone().execute::(&mut ctx).unwrap(); - primitive - .into_array() - .binary(rhs.clone(), Operator::Eq) - .unwrap() - .execute::(&mut ctx) - .unwrap() - }); -} - #[divan::bench] fn fast_lt_out_of_range(bencher: Bencher) { let (array, rhs, mut ctx) = build_inputs(); @@ -143,17 +126,3 @@ fn fast_lt_out_of_range(bencher: Bencher) { .unwrap() }); } - -#[divan::bench] -fn baseline_lt(bencher: Bencher) { - let (array, rhs, mut ctx) = build_inputs(); - bencher.bench_local(|| { - let primitive = array.clone().execute::(&mut ctx).unwrap(); - primitive - .into_array() - .binary(rhs.clone(), Operator::Lt) - .unwrap() - .execute::(&mut ctx) - .unwrap() - }); -} diff --git a/encodings/fastlanes/benches/bitpacking_take.rs b/encodings/fastlanes/benches/bitpacking_take.rs index 88576fdf669..6146f82fda2 100644 --- a/encodings/fastlanes/benches/bitpacking_take.rs +++ b/encodings/fastlanes/benches/bitpacking_take.rs @@ -429,8 +429,7 @@ fn patched_take_10k_adversarial(bencher: Bencher) { }) } -// These are the element runs emitted by a sparse FSL gather. The baseline materializes those -// runs inside the measured operation, reproducing the previous BitPacked take path. +// These are the element runs emitted by a sparse FSL gather. fn embedding_runs_fixture(dimension: usize, ptype: PType, bit_width: u8) -> (ArrayRef, ArrayRef) { const ROWS: usize = 512; const SELECTED: usize = 16; @@ -464,25 +463,13 @@ fn embedding_runs_fixture(dimension: usize, ptype: PType, bit_width: u8) -> (Arr #[divan::bench(args = [768, 1024, 1536])] fn embedding_runs(bencher: Bencher, dimension: usize) { let (packed, indices) = embedding_runs_fixture(dimension, PType::U32, 16); - bench_piecewise_take(bencher, packed, indices, false); -} - -#[divan::bench(args = [768, 1024, 1536])] -fn embedding_runs_expanded_baseline(bencher: Bencher, dimension: usize) { - let (packed, indices) = embedding_runs_fixture(dimension, PType::U32, 16); - bench_piecewise_take(bencher, packed, indices, true); + bench_piecewise_take(bencher, packed, indices); } #[divan::bench(args = [512, 1024, 2048, 4096])] fn embedding_code_runs(bencher: Bencher, dimension: usize) { let (packed, indices) = embedding_runs_fixture(dimension, PType::U8, 4); - bench_piecewise_take(bencher, packed, indices, false); -} - -#[divan::bench(args = [512, 1024, 2048, 4096])] -fn embedding_code_runs_expanded_baseline(bencher: Bencher, dimension: usize) { - let (packed, indices) = embedding_runs_fixture(dimension, PType::U8, 4); - bench_piecewise_take(bencher, packed, indices, true); + bench_piecewise_take(bencher, packed, indices); } // Short runs share chunks; long runs visit distant chunks. Neither crosses the full-decode @@ -514,13 +501,7 @@ fn fragmented_runs_fixture(run_length: usize) -> (ArrayRef, ArrayRef) { #[divan::bench(args = [1, 2, 7, 8, 64, 512, 1024])] fn fragmented_runs(bencher: Bencher, run_length: usize) { let (packed, indices) = fragmented_runs_fixture(run_length); - bench_piecewise_take(bencher, packed, indices, false); -} - -#[divan::bench(args = [1, 2, 7, 8, 64, 512, 1024])] -fn fragmented_runs_expanded_baseline(bencher: Bencher, run_length: usize) { - let (packed, indices) = fragmented_runs_fixture(run_length); - bench_piecewise_take(bencher, packed, indices, true); + bench_piecewise_take(bencher, packed, indices); } // Isolated runs cannot amortize a whole-chunk decode across neighboring runs. Offsets near @@ -565,30 +546,16 @@ fn isolated_runs(bencher: Bencher, (run_length, offset): (usize, ) .unwrap() .into_array(); - bench_piecewise_take(bencher, packed, indices, false); + bench_piecewise_take(bencher, packed, indices); } -fn bench_piecewise_take( - bencher: Bencher, - packed: ArrayRef, - indices: ArrayRef, - expanded_baseline: bool, -) { +fn bench_piecewise_take(bencher: Bencher, packed: ArrayRef, indices: ArrayRef) { bencher .counter(ItemsCount::new(indices.len())) .with_inputs(|| (indices.clone(), SESSION.create_execution_ctx())) .bench_refs(|(indices, ctx)| { - let indices = if expanded_baseline { - indices - .clone() - .execute::(ctx) - .unwrap() - .into_array() - } else { - indices.clone() - }; packed - .take(indices) + .take(indices.clone()) .unwrap() .execute::(ctx) .unwrap() diff --git a/scripts/codspeed_bench_counts.json b/scripts/codspeed_bench_counts.json index c356c1d946f..87ccb9e575b 100644 --- a/scripts/codspeed_bench_counts.json +++ b/scripts/codspeed_bench_counts.json @@ -30,7 +30,7 @@ "expr_large_struct_pack": 4, "expr_optimize": 1, "filter_bool": 61, - "filter_fixed_width": 31, + "filter_fixed_width": 30, "filter_fsl": 4, "filter_list": 12, "interleave": 18, @@ -43,7 +43,7 @@ "listview_rebuild": 9, "listview_zip": 2, "patches_lookup": 10, - "piecewise_sequence_take_primitive": 15, + "piecewise_sequence_take_primitive": 10, "primitive_zip": 2, "probe": 6, "row_fn_bool_retry": 0, @@ -60,7 +60,7 @@ "scalar_construction": 10, "scalar_fn_probe": 8, "scalar_subtract": 0, - "search_sorted": 2, + "search_sorted": 1, "slice_dict_primitive": 3, "take_chunked": 27, "take_filter": 32, @@ -74,7 +74,7 @@ "varbinview_fill_null": 2, "varbinview_try_new": 6, "varbinview_zip": 2, - "widen": 46, + "widen": 17, "widen_decimal": 6 }, "vortex-buffer": { @@ -82,8 +82,8 @@ "collect_bool": 4, "cpu_dispatch": 6, "take_bits": 0, - "vortex_bitbuffer": 101, - "vortex_buffer": 40 + "vortex_bitbuffer": 58, + "vortex_buffer": 34 }, "vortex-compressor": { "dict_encode": 0, @@ -92,7 +92,7 @@ "stats_calc": 0 }, "vortex-compute": { - "lane_kernels": 10 + "lane_kernels": 7 }, "vortex-datetime-parts": { "split_temporal": 4 @@ -108,10 +108,10 @@ "vortex-fastlanes": { "bitpack_blocked": 0, "bitpack_blocked_decompress": 0, - "bitpack_compare": 4, + "bitpack_compare": 2, "bitpack_compare_sweep": 232, "bitpacking_filter": 0, - "bitpacking_take": 54, + "bitpacking_take": 40, "canonicalize_bench": 32, "cast_bitpacked": 6, "compute_between": 33, @@ -144,7 +144,7 @@ "scalar": 4 }, "vortex-row": { - "row_encode": 10 + "row_encode": 5 }, "vortex-sparse": { "sparse_canonical": 4, diff --git a/vortex-array/benches/filter_fixed_width.rs b/vortex-array/benches/filter_fixed_width.rs index 12d98610e77..b8799dfe558 100644 --- a/vortex-array/benches/filter_fixed_width.rs +++ b/vortex-array/benches/filter_fixed_width.rs @@ -62,12 +62,11 @@ const CACHED_DENSITIES: &[f64] = &[0.01, 0.1]; #[derive(Clone, Copy, Debug)] enum Pattern { - Random, Runs, Contiguous, } -const PATTERNS: &[Pattern] = &[Pattern::Random, Pattern::Runs, Pattern::Contiguous]; +const PATTERNS: &[Pattern] = &[Pattern::Runs, Pattern::Contiguous]; fn random_mask(len: usize, density: f64) -> Mask { let threshold = (density * u64::MAX as f64) as u64; @@ -82,7 +81,6 @@ fn random_mask(len: usize, density: f64) -> Mask { fn pattern_mask(len: usize, pattern: Pattern) -> Mask { match pattern { - 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)]), } diff --git a/vortex-array/benches/piecewise_sequence_take_primitive.rs b/vortex-array/benches/piecewise_sequence_take_primitive.rs index fd7001701ac..1a17757d3cb 100644 --- a/vortex-array/benches/piecewise_sequence_take_primitive.rs +++ b/vortex-array/benches/piecewise_sequence_take_primitive.rs @@ -2,10 +2,6 @@ // SPDX-FileCopyrightText: Copyright the Vortex contributors //! Primitive take benchmarks for [`PiecewiseSequenceArray`] length representations. -//! -//! The `reify_constant_lengths_then_take` case simulates removing the constant-length -//! specialization: it starts with constant lengths, materializes them to a dense primitive array, -//! and then executes the primitive take through the non-constant-length path. #![allow(clippy::cast_possible_truncation)] #![expect(clippy::unwrap_used)] @@ -89,55 +85,6 @@ fn prebuilt_dense_lengths(bencher: Bencher, run_length: usize) { }); } -#[divan::bench(args = RUN_LENGTHS)] -fn reify_constant_lengths_then_take(bencher: Bencher, run_length: usize) { - let values = values(); - let run_count = run_count(run_length); - let starts = starts(run_count, run_length).into_array(); - let lengths = lengths_constant(run_length, run_count); - let multipliers = ConstantArray::new(1u64, run_count).into_array(); - - bencher - .counter(BytesCount::of_many::(OUTPUT_LEN)) - .with_inputs(|| { - ( - &values, - starts.clone(), - lengths.clone(), - multipliers.clone(), - SESSION.create_execution_ctx(), - ) - }) - .bench_refs(|(values, starts, lengths, multipliers, ctx)| { - let lengths = lengths - .clone() - .execute::(ctx) - .unwrap() - .into_array(); - - // SAFETY: starts, reified lengths, and unit multipliers have the same run count, are - // non-null unsigned integer arrays, and expand to OUTPUT_LEN by construction. - let indices = unsafe { - PiecewiseSequenceArray::new_unchecked( - starts.clone(), - lengths, - multipliers.clone(), - OUTPUT_LEN, - ) - } - .into_array(); - - divan::black_box( - values - .clone() - .take(indices) - .unwrap() - .execute::(ctx) - .unwrap(), - ); - }); -} - fn values() -> ArrayRef { PrimitiveArray::from_iter((0..SOURCE_LEN).map(|idx| idx as i64)).into_array() } diff --git a/vortex-array/benches/search_sorted.rs b/vortex-array/benches/search_sorted.rs index 4f339893fb7..cd7d26028bf 100644 --- a/vortex-array/benches/search_sorted.rs +++ b/vortex-array/benches/search_sorted.rs @@ -20,25 +20,10 @@ fn main() { } /// One search over 65536 elements is ~16 comparisons, small enough that the measurement is mostly -/// harness overhead and the two implementations below become indistinguishable. Search a batch per -/// iteration instead; the targets differ, so the calls cannot be folded together. +/// harness overhead. Search a batch per iteration instead; the targets differ, so the calls cannot +/// be folded together. const SEARCH_TARGETS: usize = 64; -#[cfg(not(codspeed))] -#[divan::bench] -fn binary_search_std(bencher: Bencher) { - let (sorted_array, targets) = fixture(); - bencher - .with_inputs(|| (&sorted_array, &targets)) - .bench_refs(|(array, targets)| { - let mut found = 0; - for target in targets.iter() { - found += array.binary_search(target).unwrap_or_else(|idx| idx); - } - found - }); -} - #[divan::bench] fn binary_search_vortex(bencher: Bencher) { let (sorted_array, targets) = fixture(); diff --git a/vortex-array/benches/widen.rs b/vortex-array/benches/widen.rs index 7a24650df9d..2bb0fee90c3 100644 --- a/vortex-array/benches/widen.rs +++ b/vortex-array/benches/widen.rs @@ -1,15 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -//! Compute on integer `Widen` arrays, against the canonical array with the same values. +//! Compute on integer `Widen` arrays. //! -//! Each case runs one operation on the inputs of [`Input`], which hold the same logical `i64` -//! values. `slice` and `add_canonical` omit `canonical`. A kernel for `Widen` changes only the -//! `widen` case. The `canonical` case measures the canonical path, and the `decoded` case -//! measures the fallback that decodes the `Widen` first. So the `canonical` and `decoded` cases -//! must stay flat when a kernel for `Widen` lands. +//! Each case runs one operation on a `Widen` with logical dtype `i64` over an `i16` child. +//! The `decode` case measures materialization separately from the operations that can use +//! the narrower child directly. -use std::fmt; use std::sync::LazyLock; use divan::Bencher; @@ -56,11 +53,10 @@ fn main() { // The first execution initializes process-wide state. Without this call, that work lands in the // single measured iteration of the first benchmark under the CodSpeed simulation. - let values = Values::random(0, LEN); - Input::Decoded.prepare( - values.array(Input::Widen), - &mut SESSION.create_execution_ctx(), - ); + Values::random(0, LEN) + .widen() + .execute::(&mut SESSION.create_execution_ctx()) + .vortex_expect("a Widen of i64 always executes to a PrimitiveArray"); divan::main(); } @@ -74,7 +70,7 @@ static SESSION: LazyLock = LazyLock::new(|| { .with::() }); -// The canonical input holds 256 KiB. The inputs are sized so that every case does a few +// The decoded values hold 256 KiB. The inputs are sized so that every case does a few // microseconds of real work above the CodSpeed harness floor, while the slowest case stays far // under 1 ms. const LEN: usize = 32_768; @@ -90,43 +86,6 @@ const INCLUSIVE: BetweenOptions = BetweenOptions { upper_strict: StrictComparison::NonStrict, }; -#[derive(Clone, Copy, Debug)] -enum Input { - /// The canonical `i64` array with the same values. - Canonical, - /// A `Widen` with logical dtype `i64` over an `i16` child. - Widen, - /// The `Widen`, executed to a `PrimitiveArray` inside the timed region, then the operation. - Decoded, -} - -const INPUTS: &[Input] = &[Input::Canonical, Input::Widen, Input::Decoded]; -const WIDEN_INPUTS: &[Input] = &[Input::Widen, Input::Decoded]; - -impl Input { - /// Returns `array` as the operation receives it. For `Decoded`, this decodes the `Widen`, so - /// it must run inside the timed region. - fn prepare(self, array: ArrayRef, ctx: &mut ExecutionCtx) -> ArrayRef { - match self { - Self::Canonical | Self::Widen => array, - Self::Decoded => array - .execute::(ctx) - .vortex_expect("a Widen of i64 always executes to a PrimitiveArray") - .into_array(), - } - } -} - -impl fmt::Display for Input { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match self { - Self::Canonical => "canonical", - Self::Widen => "widen", - Self::Decoded => "decoded", - }) - } -} - /// The buffers of one input, shared by every iteration. /// /// `sum` caches its result as a statistic on the array that it reads, and later kernels read @@ -164,24 +123,25 @@ impl Values { } } - /// Builds a new array for `input`. `Decoded` gets the `Widen`, which [`Input::prepare`] - /// decodes. - fn array(&self, input: Input) -> ArrayRef { - let validity = self - .validity + fn validity(&self) -> Validity { + self.validity .clone() - .map_or(Validity::NonNullable, Validity::from); - - match input { - Input::Canonical => PrimitiveArray::new(self.wide.clone(), validity).into_array(), - Input::Widen | Input::Decoded => { - let dtype = DType::Primitive(PType::I64, validity.nullability()); - let values = PrimitiveArray::new(self.narrow.clone(), validity).into_array(); - WidenArray::try_new(values, dtype) - .vortex_expect("i16 values with the nullability of the dtype can widen to i64") - .into_array() - } - } + .map_or(Validity::NonNullable, Validity::from) + } + + /// Builds a new `Widen` of the values. + fn widen(&self) -> ArrayRef { + let validity = self.validity(); + let dtype = DType::Primitive(PType::I64, validity.nullability()); + let values = PrimitiveArray::new(self.narrow.clone(), validity).into_array(); + WidenArray::try_new(values, dtype) + .vortex_expect("i16 values with the nullability of the dtype can widen to i64") + .into_array() + } + + /// Builds a new canonical `i64` array of the values. + fn canonical(&self) -> ArrayRef { + PrimitiveArray::new(self.wide.clone(), self.validity()).into_array() } } @@ -213,67 +173,55 @@ fn execute_canonical(array: VortexResult, ctx: &mut ExecutionCtx) -> C .vortex_expect("every operation executes to canonical for the benchmark inputs") } -#[divan::bench(args = INPUTS)] -fn compare_in_range(bencher: Bencher, input: Input) { +#[divan::bench] +fn compare_in_range(bencher: Bencher) { let values = Values::random(0, LEN); bench_case( bencher, - || (values.array(input), constant(0)), - |(array, zero), ctx| { - let array = input.prepare(array, ctx); - execute_canonical(array.binary(zero, Operator::Lt), ctx) - }, + || (values.widen(), constant(0)), + |(array, zero), ctx| execute_canonical(array.binary(zero, Operator::Lt), ctx), ); } -#[divan::bench(args = INPUTS)] -fn between_in_range(bencher: Bencher, input: Input) { +#[divan::bench] +fn between_in_range(bencher: Bencher) { let values = Values::random(0, LEN); bench_case( bencher, - || (values.array(input), constant(-1_000), constant(1_000)), - |(array, lower, upper), ctx| { - let array = input.prepare(array, ctx); - execute_canonical(array.between(lower, upper, INCLUSIVE), ctx) - }, + || (values.widen(), constant(-1_000), constant(1_000)), + |(array, lower, upper), ctx| execute_canonical(array.between(lower, upper, INCLUSIVE), ctx), ); } -#[divan::bench(args = INPUTS)] -fn filter(bencher: Bencher, input: Input) { +#[divan::bench] +fn filter(bencher: Bencher) { let values = Values::random(0, LEN); let selection = random_bits(2, 0.5); bench_case( bencher, - || (values.array(input), Mask::from_buffer(selection.clone())), - |(array, mask), ctx| execute_canonical(input.prepare(array, ctx).filter(mask), ctx), + || (values.widen(), Mask::from_buffer(selection.clone())), + |(array, mask), ctx| execute_canonical(array.filter(mask), ctx), ); } -/// Gathers [`LEN`] / 4 rows from [`LEN`] values. -/// -/// The `widen` input gathers the `i16` values and then widens only the gathered rows. The -/// `decoded` input widens every value before the gather. -#[divan::bench(args = INPUTS)] -fn take_few_indices(bencher: Bencher, input: Input) { +/// Gathers [`LEN`] / 4 rows from [`LEN`] values, so only the gathered rows are widened. +#[divan::bench] +fn take_few_indices(bencher: Bencher) { let values = Values::random(0, LEN); let mut rng = StdRng::seed_from_u64(3); let len = u32::try_from(LEN).vortex_expect("LEN fits u32"); let indices: Buffer = (0..LEN / 4).map(|_| rng.random_range(0..len)).collect(); bench_case( bencher, - || (values.array(input), indices.clone().into_array()), - |(array, indices), ctx| execute_canonical(input.prepare(array, ctx).take(indices), ctx), + || (values.widen(), indices.clone().into_array()), + |(array, indices), ctx| execute_canonical(array.take(indices), ctx), ); } /// Gathers [`LEN`] rows from [`FEW_VALUES_LEN`] values, as a dictionary gathers its values by its -/// codes. -/// -/// The `widen` input gathers the `i16` values and then widens every gathered row. The `decoded` -/// input widens only the few values before the gather. -#[divan::bench(args = INPUTS)] -fn take_many_indices(bencher: Bencher, input: Input) { +/// codes, so every gathered row is widened. +#[divan::bench] +fn take_many_indices(bencher: Bencher) { let values = Values::random(0, FEW_VALUES_LEN); let mut rng = StdRng::seed_from_u64(3); let indices: Buffer = (0..LEN) @@ -284,115 +232,115 @@ fn take_many_indices(bencher: Bencher, input: Input) { .collect(); bench_case( bencher, - || (values.array(input), indices.clone().into_array()), - |(array, indices), ctx| execute_canonical(input.prepare(array, ctx).take(indices), ctx), + || (values.widen(), indices.clone().into_array()), + |(array, indices), ctx| execute_canonical(array.take(indices), ctx), ); } -/// Omits the `canonical` input, because a slice of a canonical array copies nothing. -#[divan::bench(args = WIDEN_INPUTS)] -fn slice(bencher: Bencher, input: Input) { +/// Executes the `Widen` to canonical, which every operation without a kernel does first. +#[divan::bench] +fn decode(bencher: Bencher) { let values = Values::random(0, LEN); bench_case( bencher, - || values.array(input), - |array, ctx| execute_canonical(input.prepare(array, ctx).slice(LEN / 4..3 * LEN / 4), ctx), + || values.widen(), + |array, ctx| { + array + .execute::(ctx) + .vortex_expect("a Widen always executes to canonical") + }, ); } -#[divan::bench(args = INPUTS)] -fn sum(bencher: Bencher, input: Input) { +#[divan::bench] +fn slice(bencher: Bencher) { let values = Values::random(0, LEN); bench_case( bencher, - || values.array(input), - |array, ctx| { - sum::sum(&input.prepare(array, ctx), ctx) - .vortex_expect("sum is valid for every benchmark input") - }, + || values.widen(), + |array, ctx| execute_canonical(array.slice(LEN / 4..3 * LEN / 4), ctx), + ); +} + +#[divan::bench] +fn sum(bencher: Bencher) { + let values = Values::random(0, LEN); + bench_case( + bencher, + || values.widen(), + |array, ctx| sum::sum(&array, ctx).vortex_expect("sum is valid for every benchmark input"), ); } -#[divan::bench(args = INPUTS)] -fn min_max(bencher: Bencher, input: Input) { +#[divan::bench] +fn min_max(bencher: Bencher) { let values = Values::random(0, LEN); bench_case( bencher, - || values.array(input), + || values.widen(), |array, ctx| { - let array = input.prepare(array, ctx); min_max::min_max(&array, ctx, NumericalAggregateOpts::default()) .vortex_expect("min_max is valid for every benchmark input") }, ); } -#[divan::bench(args = INPUTS)] -fn add_constant(bencher: Bencher, input: Input) { +#[divan::bench] +fn add_constant(bencher: Bencher) { let values = Values::random(0, LEN); bench_case( bencher, - || (values.array(input), constant(1)), - |(array, one), ctx| { - let array = input.prepare(array, ctx); - execute_canonical(array.binary(one, Operator::Add), ctx) - }, + || (values.widen(), constant(1)), + |(array, one), ctx| execute_canonical(array.binary(one, Operator::Add), ctx), ); } -#[divan::bench(args = INPUTS)] -fn add_widen(bencher: Bencher, input: Input) { +#[divan::bench] +fn add_widen(bencher: Bencher) { let lhs = Values::random(0, LEN); let rhs = Values::random(1, LEN); bench_case( bencher, - || (lhs.array(input), rhs.array(input)), - |(lhs, rhs), ctx| { - let lhs = input.prepare(lhs, ctx); - let rhs = input.prepare(rhs, ctx); - execute_canonical(lhs.binary(rhs, Operator::Add), ctx) - }, + || (lhs.widen(), rhs.widen()), + |(lhs, rhs), ctx| execute_canonical(lhs.binary(rhs, Operator::Add), ctx), ); } /// Adds a canonical `i64` array, which a `Widen` kernel cannot read at the child width. -#[divan::bench(args = WIDEN_INPUTS)] -fn add_canonical(bencher: Bencher, input: Input) { +#[divan::bench] +fn add_canonical(bencher: Bencher) { let lhs = Values::random(0, LEN); let rhs = Values::random(1, LEN); bench_case( bencher, - || (lhs.array(input), rhs.array(Input::Canonical)), - |(lhs, rhs), ctx| { - let lhs = input.prepare(lhs, ctx); - execute_canonical(lhs.binary(rhs, Operator::Add), ctx) - }, + || (lhs.widen(), rhs.canonical()), + |(lhs, rhs), ctx| execute_canonical(lhs.binary(rhs, Operator::Add), ctx), ); } -#[divan::bench(args = INPUTS)] -fn cast_f64(bencher: Bencher, input: Input) { +#[divan::bench] +fn cast_f64(bencher: Bencher) { let values = Values::random(0, LEN); bench_case( bencher, - || values.array(input), - |array, ctx| execute_canonical(input.prepare(array, ctx).cast(F64), ctx), + || values.widen(), + |array, ctx| execute_canonical(array.cast(F64), ctx), ); } /// Fills the nulls with a value that fits `i32` but not the `i16` child. -#[divan::bench(args = INPUTS)] -fn fill_null_out_of_range(bencher: Bencher, input: Input) { +#[divan::bench] +fn fill_null_out_of_range(bencher: Bencher) { let values = Values::random(0, LEN).with_validity(random_bits(4, 0.9)); bench_case( bencher, - || values.array(input), - |array, ctx| execute_canonical(input.prepare(array, ctx).fill_null(100_000i64), ctx), + || values.widen(), + |array, ctx| execute_canonical(array.fill_null(100_000i64), ctx), ); } -#[divan::bench(args = INPUTS)] -fn zip(bencher: Bencher, input: Input) { +#[divan::bench] +fn zip(bencher: Bencher) { let if_true = Values::random(0, LEN); let if_false = Values::random(1, LEN); let selection = random_bits(2, 0.5); @@ -400,33 +348,20 @@ fn zip(bencher: Bencher, input: Input) { bencher, || { let mask = selection.clone().into_array(); - (mask, if_true.array(input), if_false.array(input)) - }, - |(mask, if_true, if_false), ctx| { - let if_true = input.prepare(if_true, ctx); - let if_false = input.prepare(if_false, ctx); - execute_canonical(mask.zip(if_true, if_false), ctx) + (mask, if_true.widen(), if_false.widen()) }, + |(mask, if_true, if_false), ctx| execute_canonical(mask.zip(if_true, if_false), ctx), ); } -/// Canonicalizes a chunked array of four chunks, whose chunks are `Widen` for the `widen` input. -#[divan::bench(args = INPUTS)] -fn chunked(bencher: Bencher, input: Input) { +/// Canonicalizes a chunked array of four `Widen` chunks. +#[divan::bench] +fn chunked(bencher: Bencher) { let chunks: Vec = (0..4).map(|seed| Values::random(seed, LEN / 4)).collect(); bench_case( bencher, - || { - chunks - .iter() - .map(|chunk| chunk.array(input)) - .collect::>() - }, + || chunks.iter().map(Values::widen).collect::>(), |chunks, ctx| { - let chunks: Vec = chunks - .into_iter() - .map(|chunk| input.prepare(chunk, ctx)) - .collect(); execute_canonical( ChunkedArray::try_new(chunks, I64).map(IntoArray::into_array), ctx, @@ -436,8 +371,8 @@ fn chunked(bencher: Bencher, input: Input) { } /// Merges two inputs row by row. -#[divan::bench(args = INPUTS)] -fn interleave(bencher: Bencher, input: Input) { +#[divan::bench] +fn interleave(bencher: Bencher) { let first = Values::random(0, LEN / 2); let second = Values::random(1, LEN / 2); let array_indices: Buffer = (0..LEN / 2).flat_map(|_| [0, 1]).collect(); @@ -445,15 +380,13 @@ fn interleave(bencher: Bencher, input: Input) { bench_case( bencher, || { - let values = [first.array(input), second.array(input)]; ( - values, + vec![first.widen(), second.widen()], array_indices.clone().into_array(), row_indices.clone().into_array(), ) }, - |([first, second], array_indices, row_indices), ctx| { - let values = vec![input.prepare(first, ctx), input.prepare(second, ctx)]; + |(values, array_indices, row_indices), ctx| { let interleaved = InterleaveArray::try_new(values, array_indices, row_indices); execute_canonical(interleaved.map(IntoArray::into_array), ctx) }, diff --git a/vortex-buffer/benches/vortex_bitbuffer.rs b/vortex-buffer/benches/vortex_bitbuffer.rs index 48d48d83d85..3a95453a0e4 100644 --- a/vortex-buffer/benches/vortex_bitbuffer.rs +++ b/vortex-buffer/benches/vortex_bitbuffer.rs @@ -3,8 +3,6 @@ use std::iter::Iterator; -use arrow_buffer::BooleanBuffer; -use arrow_buffer::BooleanBufferBuilder; use divan::Bencher; use mimalloc::MiMalloc; use vortex_buffer::BitBuffer; @@ -32,15 +30,6 @@ fn main() { divan::main(); } -/// Wraps an arrow buffer so Divan can provide a nice name -pub struct Arrow(T); - -impl FromIterator for Arrow { - fn from_iter>(iter: I) -> Self { - Self(BooleanBuffer::from_iter(iter)) - } -} - const INPUT_SIZE: &[usize] = &[128, 1024, 2048, 16_384, 65_536]; #[inline] @@ -48,12 +37,6 @@ fn true_count_pattern(i: usize) -> bool { (i.is_multiple_of(3)) ^ (i.is_multiple_of(11)) } -#[cfg(not(codspeed))] -#[divan::bench(args = INPUT_SIZE)] -fn from_iter_arrow(n: usize) -> Arrow { - Arrow::::from_iter((0..n).map(|i| i % 2 == 0)) -} - #[divan::bench(args = INPUT_SIZE)] fn from_iter_bit_buffer(n: usize) -> BitBuffer { BitBuffer::from_iter((0..n).map(|i| i % 2 == 0)) @@ -70,17 +53,6 @@ fn append_vortex_buffer(bencher: Bencher, length: usize) { }); } -#[divan::bench(args = INPUT_SIZE)] -fn append_arrow_buffer(bencher: Bencher, length: usize) { - bencher - .with_inputs(|| (Arrow(BooleanBufferBuilder::new(length)), length)) - .bench_refs(|(buffer, length)| { - for idx in 0..*length { - buffer.0.append(idx % 2 == 0); - } - }); -} - const CHUNKED_APPEND_INPUT_SIZE: &[usize] = &[2_048, 16_384, 65_536]; #[divan::bench(args = CHUNKED_APPEND_INPUT_SIZE)] @@ -94,17 +66,6 @@ fn append_n_vortex_buffer(bencher: Bencher, length: usize) { }); } -#[divan::bench(args = CHUNKED_APPEND_INPUT_SIZE)] -fn append_n_arrow_buffer(bencher: Bencher, length: usize) { - bencher - .with_inputs(|| (Arrow(BooleanBufferBuilder::new(length)), length, true)) - .bench_refs(|(buffer, length, boolean)| { - for _ in 0..100 { - buffer.0.append_n(*length / 100, *boolean); - } - }); -} - #[divan::bench(args = CHUNKED_APPEND_INPUT_SIZE)] fn append_buffer_vortex_buffer(bencher: Bencher, length: usize) { bencher @@ -120,29 +81,6 @@ fn append_buffer_vortex_buffer(bencher: Bencher, length: usize) { }); } -/// Arrow has no bulk append from a `BooleanBuffer`, so this appends bit-by-bit and runs an -/// order of magnitude slower than the vortex bulk path; cap the top size accordingly. -const APPEND_BUFFER_ARROW_INPUT_SIZE: &[usize] = &[2_048, 16_384, 32_768]; - -#[divan::bench(args = APPEND_BUFFER_ARROW_INPUT_SIZE)] -fn append_buffer_arrow_buffer(bencher: Bencher, length: usize) { - bencher - .with_inputs(|| { - let source = Arrow(BooleanBuffer::from_iter( - (0..length / 100).map(|i| i % 2 == 0), - )); - let dest = Arrow(BooleanBufferBuilder::new(length)); - (source, dest) - }) - .bench_refs(|(source, dest)| { - for _ in 0..100 { - for value in source.0.iter() { - dest.0.append(value); - } - } - }); -} - #[divan::bench(args = INPUT_SIZE)] fn value_vortex_buffer(bencher: Bencher, length: usize) { let buffer = BitBuffer::from_iter((0..length).map(|i| i % 2 == 0)); @@ -153,16 +91,6 @@ fn value_vortex_buffer(bencher: Bencher, length: usize) { }); } -#[divan::bench(args = INPUT_SIZE)] -fn value_arrow_buffer(bencher: Bencher, length: usize) { - let buffer = Arrow(BooleanBuffer::from_iter((0..length).map(|i| i % 2 == 0))); - bencher.with_inputs(|| &buffer).bench_refs(|buffer| { - for idx in 0..length { - divan::black_box(buffer.0.value(idx)); - } - }); -} - /// Slicing only adjusts an offset, a length and a refcount, so its cost is independent of buffer /// length. Measure one length, with enough slices per iteration to stay above harness overhead. const SLICE_ITERS: usize = 64; @@ -181,22 +109,6 @@ fn slice_vortex_buffer(bencher: Bencher) { }); } -#[cfg(not(codspeed))] -#[divan::bench] -fn slice_arrow_buffer(bencher: Bencher) { - let buffer = Arrow(BooleanBuffer::from_iter( - (0..SLICE_INPUT_SIZE).map(|i| i % 2 == 0), - )); - let mid = SLICE_INPUT_SIZE / 2; - bencher.with_inputs(|| &buffer).bench_refs(|buffer| { - let mut total = 0; - for offset in 0..SLICE_ITERS { - total += buffer.0.slice(mid / 2 + offset, mid / 2).len(); - } - total - }); -} - /// Below a few thousand bits the measurement is mostly divan's fixed per-iteration overhead /// rather than the popcount, so it moves when the count itself has not changed. const TRUE_COUNT_INPUT_SIZE: &[usize] = &[16_384, 65_536]; @@ -210,19 +122,6 @@ fn true_count_vortex_buffer(bencher: Bencher, length: usize) { .bench_refs(|buffer| buffer.true_count()) } -#[divan::bench(args = TRUE_COUNT_INPUT_SIZE)] -fn true_count_arrow_buffer(bencher: Bencher, length: usize) { - let buffer = Arrow(BooleanBuffer::from_iter( - (0..length).map(true_count_pattern), - )); - - buffer.0.count_set_bits(); - - bencher - .with_inputs(|| &buffer) - .bench_refs(|buffer| buffer.0.count_set_bits()); -} - #[divan::bench(args = INPUT_SIZE)] fn bitwise_and_vortex_buffer(bencher: Bencher, length: usize) { let a = BitBuffer::from_iter((0..length).map(|i| i % 2 == 0)); @@ -232,15 +131,6 @@ fn bitwise_and_vortex_buffer(bencher: Bencher, length: usize) { .bench_values(|(a, b)| a & b); } -#[divan::bench(args = INPUT_SIZE)] -fn bitwise_and_arrow_buffer(bencher: Bencher, length: usize) { - let a = Arrow(BooleanBuffer::from_iter((0..length).map(|i| i % 2 == 0))); - let b = Arrow(BooleanBuffer::from_iter((0..length).map(|i| i % 3 == 0))); - bencher - .with_inputs(|| (&a, &b)) - .bench_refs(|(a, b)| &a.0 & &b.0); -} - /// Owned-LHS AND: the left operand is a fresh, uniquely-owned `BitBuffer` each iteration, so /// `bitwise_binary_op_lhs_owned` takes the in-place (zero-allocation) fast path. Compare against /// `bitwise_and_vortex_buffer` (reference-LHS, which always allocates a result buffer). @@ -261,15 +151,6 @@ fn bitwise_or_vortex_buffer(bencher: Bencher, length: usize) { .bench_values(|(a, b)| a | b); } -#[divan::bench(args = INPUT_SIZE)] -fn bitwise_or_arrow_buffer(bencher: Bencher, length: usize) { - let a = Arrow(BooleanBuffer::from_iter((0..length).map(|i| i % 2 == 0))); - let b = Arrow(BooleanBuffer::from_iter((0..length).map(|i| i % 3 == 0))); - bencher - .with_inputs(|| (&a, &b)) - .bench_refs(|(a, b)| &a.0 | &b.0); -} - #[divan::bench(args = INPUT_SIZE)] fn bitwise_not_vortex_buffer(bencher: Bencher, length: usize) { bencher @@ -289,13 +170,6 @@ fn bitwise_not_vortex_buffer_mut(bencher: Bencher, length: usize) { .bench_values(|buffer| !buffer); } -#[divan::bench(args = INPUT_SIZE)] -fn bitwise_not_arrow_buffer(bencher: Bencher, length: usize) { - bencher - .with_inputs(|| Arrow(BooleanBuffer::from_iter((0..length).map(|i| i % 2 == 0)))) - .bench_values(|buffer| !&buffer.0); -} - #[divan::bench(args = INPUT_SIZE)] fn iter_vortex_buffer(bencher: Bencher, length: usize) { let buffer = BitBuffer::from_iter((0..length).map(|i| i % 2 == 0)); @@ -306,16 +180,6 @@ fn iter_vortex_buffer(bencher: Bencher, length: usize) { }); } -#[divan::bench(args = INPUT_SIZE)] -fn iter_arrow_buffer(bencher: Bencher, length: usize) { - let buffer = Arrow(BooleanBuffer::from_iter((0..length).map(|i| i % 2 == 0))); - bencher.with_inputs(|| &buffer).bench_refs(|buffer| { - for value in buffer.0.iter() { - divan::black_box(value); - } - }); -} - #[divan::bench(args = INPUT_SIZE)] fn set_indices_vortex_buffer(bencher: Bencher, length: usize) { let buffer = BitBuffer::from_iter((0..length).map(|i| i % 2 == 0)); @@ -326,16 +190,6 @@ fn set_indices_vortex_buffer(bencher: Bencher, length: usize) { }); } -#[divan::bench(args = INPUT_SIZE)] -fn set_indices_arrow_buffer(bencher: Bencher, length: usize) { - let buffer = Arrow(BooleanBuffer::from_iter((0..length).map(|i| i % 2 == 0))); - bencher.with_inputs(|| &buffer).bench_refs(|buffer| { - for idx in buffer.0.set_indices() { - divan::black_box(idx); - } - }); -} - /// Rank-select scan over a sparse deterministic bitmap, reaching the final chunk. #[divan::bench(args = [65_536, 1_048_576])] fn select_last_vortex_buffer(bencher: Bencher, length: usize) { diff --git a/vortex-buffer/benches/vortex_buffer.rs b/vortex-buffer/benches/vortex_buffer.rs index d15287fde84..ab35bc8bc7c 100644 --- a/vortex-buffer/benches/vortex_buffer.rs +++ b/vortex-buffer/benches/vortex_buffer.rs @@ -3,9 +3,6 @@ use std::iter::Iterator; -use arrow_buffer::ArrowNativeType; -use arrow_buffer::MutableBuffer; -use arrow_buffer::ScalarBuffer; use divan::Bencher; use mimalloc::MiMalloc; use num_traits::PrimInt; @@ -20,26 +17,11 @@ fn main() { divan::main(); } -/// Wraps an arrow buffer so Divan can provide a nice name -pub struct Arrow(T); - -impl FromIterator for Arrow> { - fn from_iter>(iter: I) -> Self { - Self(ScalarBuffer::from_iter(iter)) - } -} - -impl FromIterator for Arrow { - fn from_iter>(iter: I) -> Self { - Self(MutableBuffer::from_iter(iter)) - } -} - const INPUT_SIZE: &[i32] = &[128, 1024, 2048, 16_384, 65_536]; const PUSH_N_INPUT_SIZE: &[usize] = &[2_048, 16_384, 65_536]; #[divan::bench( - types = [Arrow>,Buffer], + types = [Buffer], args = INPUT_SIZE, )] fn from_iter>(n: i32) -> B { @@ -61,21 +43,6 @@ fn push_vortex_buffer(bencher: Bencher, length: i32) { }); } -#[divan::bench(args = PUSH_INPUT_SIZE)] -fn push_arrow_buffer(bencher: Bencher, length: i32) { - bencher - .with_inputs(|| { - Arrow(MutableBuffer::with_capacity( - length as usize * size_of::(), - )) - }) - .bench_refs(|buffer| { - for idx in 0..length { - buffer.0.push(divan::black_box(idx)); - } - }); -} - #[divan::bench(types = [u8, u16, u32, u64], args = PUSH_N_INPUT_SIZE)] fn push_n_vortex_buffer(bencher: Bencher, length: usize) { bencher @@ -103,20 +70,6 @@ fn slice_tight_loop_vortex(bencher: Bencher, len: usize) { }); } -#[divan::bench(args = &[65_536usize])] -fn slice_tight_loop_arrow(bencher: Bencher, len: usize) { - let buf = ScalarBuffer::::from_iter( - (0..i32::try_from(len).vortex_expect("len fits into i32")).map(|i| i % i32::MAX), - ); - bencher.bench(|| { - let mut offset = 0; - while offset + SLICE_WINDOW <= len { - divan::black_box(buf.slice(offset, SLICE_WINDOW)); - offset += SLICE_WINDOW; - } - }); -} - /// Loops like `slice_tight_loop_vortex` above: a single empty slice is a few nanoseconds, far /// below the measurement noise floor, so the loop amortizes fixed overhead until the slice /// itself dominates. diff --git a/vortex-compute/Cargo.toml b/vortex-compute/Cargo.toml index fc90b7029df..bb3bb830b15 100644 --- a/vortex-compute/Cargo.toml +++ b/vortex-compute/Cargo.toml @@ -19,11 +19,6 @@ all-features = true vortex-buffer = { workspace = true } [dev-dependencies] -arrow-arith = { workspace = true } -arrow-array = { workspace = true } -arrow-buffer = { workspace = true } -arrow-cast = { workspace = true } -arrow-schema = { workspace = true } divan = { workspace = true } mimalloc = { workspace = true } num-traits = { workspace = true } diff --git a/vortex-compute/benches/lane_kernels.rs b/vortex-compute/benches/lane_kernels.rs index ba78c9f6d45..412a854e1f5 100644 --- a/vortex-compute/benches/lane_kernels.rs +++ b/vortex-compute/benches/lane_kernels.rs @@ -9,36 +9,14 @@ //! - 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. -//! //! `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. +//! differently per leg. The Vortex cast benches stay in simulation. #![expect(clippy::unwrap_used)] -#![expect(clippy::clone_on_ref_ptr)] use std::mem::MaybeUninit; -use std::sync::Arc; - -#[cfg(not(codspeed))] -use arrow_arith::numeric::add; -use arrow_array::ArrayRef as ArrowArrayRef; -use arrow_array::Int32Array; -use arrow_array::UInt16Array; -#[cfg(not(codspeed))] -use arrow_array::UInt32Array; -use arrow_array::UInt64Array; -use arrow_buffer::NullBuffer; -use arrow_cast::CastOptions; -use arrow_cast::cast_with_options; -use arrow_schema::DataType; + use divan::Bencher; use mimalloc::MiMalloc; use num_traits::AsPrimitive; @@ -85,9 +63,6 @@ struct CastFixture { /// in-place-vs-out-of-place cast bench. values_i32: Buffer, mask: BitBuffer, - /// Validity as a plain `Vec` — the source of truth used to build both - /// the Vortex `BitBuffer` mask and the arrow `NullBuffer`. - valid: Vec, } fn cast_fixture(n: usize) -> CastFixture { @@ -118,7 +93,6 @@ fn cast_fixture(n: usize) -> CastFixture { values_u16, values_i32, mask: BitBufferMut::from_iter(raw_valid.iter().copied()).freeze(), - valid: raw_valid, } } @@ -150,20 +124,6 @@ fn try_map_into_narrow_u64_u32(bencher: Bencher, n: usize) { }); } -#[divan::bench(args = SIZES)] -fn arrow_narrow_u64_u32(bencher: Bencher, n: usize) { - let f = cast_fixture(n); - let arr: ArrowArrayRef = Arc::new(UInt64Array::from(f.values_u64.as_slice().to_vec())); - let opts = CastOptions { - safe: false, - ..CastOptions::default() - }; - - bencher - .with_inputs(|| arr.clone()) - .bench_values(|arr| cast_with_options(&arr, &DataType::UInt32, &opts).unwrap()); -} - #[divan::bench(args = SIZES)] fn map_with_mask_narrow_u64_u32(bencher: Bencher, n: usize) { let f = cast_fixture(n); @@ -219,20 +179,6 @@ fn map_with_mask_widen_u16_u32(bencher: Bencher, n: usize) { }); } -#[divan::bench(args = SIZES)] -fn arrow_widen_u16_u32(bencher: Bencher, n: usize) { - let f = cast_fixture(n); - let nulls = NullBuffer::from(f.valid.clone()); - let arr: ArrowArrayRef = Arc::new(UInt16Array::new( - f.values_u16.as_slice().to_vec().into(), - Some(nulls), - )); - - bencher.with_inputs(|| arr.clone()).bench_values(|arr| { - cast_with_options(&arr, &DataType::UInt32, &CastOptions::default()).unwrap() - }); -} - // ----------------------------------------------------------------------------- // In-place vs out-of-place fallible cast i32 → u32 (same byte width). // @@ -272,24 +218,6 @@ fn try_map_masked_in_place_narrow_i32_u32(bencher: Bencher, n: usize) { }); } -#[divan::bench(args = SIZES)] -fn arrow_narrow_i32_u32(bencher: Bencher, n: usize) { - let f = cast_fixture(n); - let nulls = NullBuffer::from(f.valid.clone()); - let arr: ArrowArrayRef = Arc::new(Int32Array::new( - f.values_i32.as_slice().to_vec().into(), - Some(nulls), - )); - let opts = CastOptions { - safe: false, - ..CastOptions::default() - }; - - bencher - .with_inputs(|| arr.clone()) - .bench_values(|arr| cast_with_options(&arr, &DataType::UInt32, &opts).unwrap()); -} - // ----------------------------------------------------------------------------- // LaneZip binary kernel: checked `u32 + u32 -> u32` over two nullable columns. // @@ -310,12 +238,6 @@ struct AddFixture { rhs: Buffer, lhs_mask: BitBuffer, rhs_mask: BitBuffer, - /// Plain `Vec` mirrors of the validity masks — used to build the arrow - /// `NullBuffer`s for the baseline bench. - #[cfg(not(codspeed))] - lhs_valid: Vec, - #[cfg(not(codspeed))] - rhs_valid: Vec, } fn add_fixture(n: usize) -> AddFixture { @@ -358,10 +280,6 @@ fn add_fixture(n: usize) -> AddFixture { rhs, lhs_mask, rhs_mask, - #[cfg(not(codspeed))] - lhs_valid, - #[cfg(not(codspeed))] - rhs_valid, } } @@ -388,24 +306,6 @@ fn lanezip_checked_add_u32(bencher: Bencher, n: usize) { }); } -#[cfg(not(codspeed))] -#[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( - f.lhs.as_slice().to_vec().into(), - Some(NullBuffer::from(f.lhs_valid.clone())), - )); - let rhs_arr: ArrowArrayRef = Arc::new(UInt32Array::new( - f.rhs.as_slice().to_vec().into(), - Some(NullBuffer::from(f.rhs_valid.clone())), - )); - - bencher - .with_inputs(|| (lhs_arr.clone(), rhs_arr.clone())) - .bench_values(|(lhs, rhs)| add(&lhs, &rhs).unwrap()); -} - // ----------------------------------------------------------------------------- // Parity assertions — must pass before divan runs benches. // ----------------------------------------------------------------------------- diff --git a/vortex-row/Cargo.toml b/vortex-row/Cargo.toml index d37f9d4ac62..6a1372ee5ed 100644 --- a/vortex-row/Cargo.toml +++ b/vortex-row/Cargo.toml @@ -28,7 +28,6 @@ vortex-session = { workspace = true } [dev-dependencies] arrow-array = { workspace = true } arrow-row = { workspace = true } -arrow-schema = { workspace = true } divan = { workspace = true } mimalloc = { workspace = true } rand = { workspace = true } diff --git a/vortex-row/benches/row_encode.rs b/vortex-row/benches/row_encode.rs index 7bf91245e9e..1795fbc5c00 100644 --- a/vortex-row/benches/row_encode.rs +++ b/vortex-row/benches/row_encode.rs @@ -4,30 +4,25 @@ #![expect( clippy::unwrap_used, clippy::expect_used, - clippy::clone_on_ref_ptr, clippy::cloned_ref_to_slice_refs, clippy::chunks_exact_to_as_chunks, clippy::redundant_clone )] -//! Row-encode throughput benchmarks comparing `arrow-row` against Vortex's [`RowEncoder`] -//! for the core canonical scenarios: a primitive i64 column, a Utf8 column, and a -//! mixed-field struct. +//! Row-encode throughput benchmarks for Vortex's [`RowEncoder`] over the core canonical +//! scenarios: a primitive i64 column, a Utf8 column, a mixed-field struct, and lists. +//! +//! Run with `--output-sizes` to print the encoded size of each list case next to `arrow-row`. use std::sync::Arc; use std::sync::LazyLock; use arrow_array::Array; -use arrow_array::Int64Array; -use arrow_array::StringArray; -use arrow_array::StructArray as ArrowStructArray; use arrow_array::builder::Int64Builder; use arrow_array::builder::ListBuilder as ArrowListBuilder; use arrow_array::builder::StringBuilder; use arrow_row::RowConverter; use arrow_row::SortField as ArrowSortField; -use arrow_schema::DataType; -use arrow_schema::Field; use divan::counter::BytesCount; use mimalloc::MiMalloc; use rand::RngExt; @@ -164,17 +159,6 @@ fn print_list_output_sizes() { // ---------- primitive_i64 ---------- -#[divan::bench] -fn primitive_i64_arrow_row(bencher: divan::Bencher) { - let v = gen_i64(N, 0); - let arr = Arc::new(Int64Array::from(v.clone())) as arrow_array::ArrayRef; - let conv = RowConverter::new(vec![ArrowSortField::new(DataType::Int64)]).unwrap(); - let bytes = (N * (1 + 8)) as u64; - bencher - .counter(BytesCount::new(bytes)) - .bench_local(|| conv.convert_columns(&[arr.clone()]).unwrap()) -} - #[divan::bench] fn primitive_i64_vortex(bencher: divan::Bencher) { let v = gen_i64(N, 0); @@ -189,20 +173,6 @@ fn primitive_i64_vortex(bencher: divan::Bencher) { // ---------- utf8 ---------- -#[divan::bench] -fn utf8_arrow_row(bencher: divan::Bencher) { - let words = gen_words(N, 16, 7); - let total: u64 = words - .iter() - .map(|w| 1 + (w.len().div_ceil(32) * 33) as u64) - .sum(); - let arr = Arc::new(StringArray::from(words.clone())) as arrow_array::ArrayRef; - let conv = RowConverter::new(vec![ArrowSortField::new(DataType::Utf8)]).unwrap(); - bencher - .counter(BytesCount::new(total)) - .bench_local(|| conv.convert_columns(&[arr.clone()]).unwrap()) -} - #[divan::bench] fn utf8_vortex(bencher: divan::Bencher) { let words = gen_words(N, 16, 7); @@ -233,31 +203,6 @@ fn struct_mixed_inputs() -> (Vec, Vec, u64) { (ids, names, total) } -#[divan::bench] -fn struct_mixed_arrow_row(bencher: divan::Bencher) { - let (ids, names, total) = struct_mixed_inputs(); - let id_arr = Arc::new(Int64Array::from(ids)) as arrow_array::ArrayRef; - let name_arr = Arc::new(StringArray::from(names)) as arrow_array::ArrayRef; - let arrow_struct = Arc::new(ArrowStructArray::from(vec![ - (Arc::new(Field::new("id", DataType::Int64, false)), id_arr), - ( - Arc::new(Field::new("name", DataType::Utf8, false)), - name_arr, - ), - ])) as arrow_array::ArrayRef; - let struct_fields = vec![ - Arc::new(Field::new("id", DataType::Int64, false)), - Arc::new(Field::new("name", DataType::Utf8, false)), - ]; - let conv = RowConverter::new(vec![ArrowSortField::new(DataType::Struct( - struct_fields.into(), - ))]) - .unwrap(); - bencher - .counter(BytesCount::new(total)) - .bench_local(|| conv.convert_columns(&[arrow_struct.clone()]).unwrap()) -} - #[divan::bench] fn struct_mixed_vortex(bencher: divan::Bencher) { let (ids, names, total) = struct_mixed_inputs(); @@ -275,16 +220,6 @@ fn struct_mixed_vortex(bencher: divan::Bencher) { // ---------- list_i64 ---------- -#[divan::bench] -fn list_i64_arrow_row(bencher: divan::Bencher) { - let (arr, _) = list_i64_inputs(); - let conv = RowConverter::new(vec![ArrowSortField::new(arr.data_type().clone())]).unwrap(); - let output_bytes = arrow_output_bytes(&conv.convert_columns(&[arr.clone()]).unwrap()); - bencher - .counter(BytesCount::new(output_bytes)) - .bench_local(|| conv.convert_columns(&[arr.clone()]).unwrap()) -} - #[divan::bench] fn list_i64_vortex(bencher: divan::Bencher) { let (_, list) = list_i64_inputs(); @@ -299,16 +234,6 @@ fn list_i64_vortex(bencher: divan::Bencher) { // ---------- list_utf8 ---------- -#[divan::bench] -fn list_utf8_arrow_row(bencher: divan::Bencher) { - let (arr, _) = list_utf8_inputs(); - let conv = RowConverter::new(vec![ArrowSortField::new(arr.data_type().clone())]).unwrap(); - let output_bytes = arrow_output_bytes(&conv.convert_columns(&[arr.clone()]).unwrap()); - bencher - .counter(BytesCount::new(output_bytes)) - .bench_local(|| conv.convert_columns(&[arr.clone()]).unwrap()) -} - #[divan::bench] fn list_utf8_vortex(bencher: divan::Bencher) { let (_, list) = list_utf8_inputs();