From 2419404c77e4b833d4ef405d2de6b03e94972820 Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Mon, 13 Jul 2026 16:47:43 +0300 Subject: [PATCH 1/4] remove all sort benches --- datafusion/core/benches/sort.rs | 93 +-------------------------------- 1 file changed, 2 insertions(+), 91 deletions(-) diff --git a/datafusion/core/benches/sort.rs b/datafusion/core/benches/sort.rs index 4c4cb2ea1ec92..81751f4a36f4d 100644 --- a/datafusion/core/benches/sort.rs +++ b/datafusion/core/benches/sort.rs @@ -157,96 +157,7 @@ type PartitionedBatches = Vec>; type StreamGenerator = Box PartitionedBatches>; fn criterion_benchmark(c: &mut Criterion) { - for &(input_size, size_label) in INPUT_SIZES { - let cases: Vec<(&str, StreamGenerator)> = vec![ - ( - "i64", - Box::new(move |sorted| i64_streams(sorted, input_size)), - ), - ( - "f64", - Box::new(move |sorted| f64_streams(sorted, input_size)), - ), - ( - "utf8 low cardinality", - Box::new(move |sorted| utf8_low_cardinality_streams(sorted, input_size)), - ), - ( - "utf8 high cardinality", - Box::new(move |sorted| utf8_high_cardinality_streams(sorted, input_size)), - ), - ( - "utf8 view low cardinality", - Box::new(move |sorted| { - utf8_view_low_cardinality_streams(sorted, input_size) - }), - ), - ( - "utf8 view high cardinality", - Box::new(move |sorted| { - utf8_view_high_cardinality_streams(sorted, input_size) - }), - ), - ( - "utf8 tuple", - Box::new(move |sorted| utf8_tuple_streams(sorted, input_size)), - ), - ( - "utf8 view tuple", - Box::new(move |sorted| utf8_view_tuple_streams(sorted, input_size)), - ), - ( - "utf8 dictionary", - Box::new(move |sorted| dictionary_streams(sorted, input_size)), - ), - ( - "utf8 dictionary tuple", - Box::new(move |sorted| dictionary_tuple_streams(sorted, input_size)), - ), - ( - "mixed dictionary tuple", - Box::new(move |sorted| { - mixed_dictionary_tuple_streams(sorted, input_size) - }), - ), - ( - "mixed tuple", - Box::new(move |sorted| mixed_tuple_streams(sorted, input_size)), - ), - ( - "mixed tuple with utf8 view", - Box::new(move |sorted| { - mixed_tuple_with_utf8_view_streams(sorted, input_size) - }), - ), - ]; - - for (name, f) in &cases { - c.bench_function(&format!("merge sorted {name} {size_label}"), |b| { - let data = f(true); - let case = BenchCase::merge_sorted(&data); - b.iter(move || case.run()) - }); - - c.bench_function(&format!("sort merge {name} {size_label}"), |b| { - let data = f(false); - let case = BenchCase::sort_merge(&data); - b.iter(move || case.run()) - }); - - c.bench_function(&format!("sort {name} {size_label}"), |b| { - let data = f(false); - let case = BenchCase::sort(&data); - b.iter(move || case.run()) - }); - - c.bench_function(&format!("sort partitioned {name} {size_label}"), |b| { - let data = f(false); - let case = BenchCase::sort_partitioned(&data); - b.iter(move || case.run()) - }); - } - } + // s.be } /// Encapsulates running each test case @@ -989,5 +900,5 @@ fn with_extra_columns(batches: PartitionedBatches, n: usize) -> PartitionedBatch .collect() } -criterion_group!(benches, criterion_benchmark, sort_axis_benchmark); +criterion_group!(benches, criterion_benchmark); criterion_main!(benches); From 5b1aa71e7c8d57f1da954c8ecf8016afafa4be69 Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Mon, 13 Jul 2026 22:40:41 +0300 Subject: [PATCH 2/4] try --- datafusion/core/benches/sort.rs | 91 ++++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/datafusion/core/benches/sort.rs b/datafusion/core/benches/sort.rs index 81751f4a36f4d..181d1b564b34e 100644 --- a/datafusion/core/benches/sort.rs +++ b/datafusion/core/benches/sort.rs @@ -157,7 +157,96 @@ type PartitionedBatches = Vec>; type StreamGenerator = Box PartitionedBatches>; fn criterion_benchmark(c: &mut Criterion) { - // s.be + for &(input_size, size_label) in INPUT_SIZES { + let cases: Vec<(&str, StreamGenerator)> = vec![ + ( + "i64", + Box::new(move |sorted| i64_streams(sorted, input_size)), + ), + // ( + // "f64", + // Box::new(move |sorted| f64_streams(sorted, input_size)), + // ), + // ( + // "utf8 low cardinality", + // Box::new(move |sorted| utf8_low_cardinality_streams(sorted, input_size)), + // ), + // ( + // "utf8 high cardinality", + // Box::new(move |sorted| utf8_high_cardinality_streams(sorted, input_size)), + // ), + // ( + // "utf8 view low cardinality", + // Box::new(move |sorted| { + // utf8_view_low_cardinality_streams(sorted, input_size) + // }), + // ), + // ( + // "utf8 view high cardinality", + // Box::new(move |sorted| { + // utf8_view_high_cardinality_streams(sorted, input_size) + // }), + // ), + // ( + // "utf8 tuple", + // Box::new(move |sorted| utf8_tuple_streams(sorted, input_size)), + // ), + // ( + // "utf8 view tuple", + // Box::new(move |sorted| utf8_view_tuple_streams(sorted, input_size)), + // ), + // ( + // "utf8 dictionary", + // Box::new(move |sorted| dictionary_streams(sorted, input_size)), + // ), + // ( + // "utf8 dictionary tuple", + // Box::new(move |sorted| dictionary_tuple_streams(sorted, input_size)), + // ), + // ( + // "mixed dictionary tuple", + // Box::new(move |sorted| { + // mixed_dictionary_tuple_streams(sorted, input_size) + // }), + // ), + // ( + // "mixed tuple", + // Box::new(move |sorted| mixed_tuple_streams(sorted, input_size)), + // ), + // ( + // "mixed tuple with utf8 view", + // Box::new(move |sorted| { + // mixed_tuple_with_utf8_view_streams(sorted, input_size) + // }), + // ), + ]; + + for (name, f) in &cases { + c.bench_function(&format!("merge sorted {name} {size_label}"), |b| { + let data = f(true); + let case = BenchCase::merge_sorted(&data); + b.iter(move || case.run()) + }); + + c.bench_function(&format!("sort merge {name} {size_label}"), |b| { + let data = f(false); + let case = BenchCase::sort_merge(&data); + b.iter(move || case.run()) + }); + + c.bench_function(&format!("sort {name} {size_label}"), |b| { + let data = f(false); + let case = BenchCase::sort(&data); + b.iter(move || case.run()) + }); + + c.bench_function(&format!("sort partitioned {name} {size_label}"), |b| { + let data = f(false); + let case = BenchCase::sort_partitioned(&data); + b.iter(move || case.run()) + }); + } + } } /// Encapsulates running each test case From cfc63e92c1dfb46c74719b4afffa5b6618b68c1f Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Tue, 14 Jul 2026 17:37:58 +0300 Subject: [PATCH 3/4] reduce --- datafusion/core/benches/sort.rs | 164 +++++++++++++++++++------------- 1 file changed, 100 insertions(+), 64 deletions(-) diff --git a/datafusion/core/benches/sort.rs b/datafusion/core/benches/sort.rs index 181d1b564b34e..51edd97a3966c 100644 --- a/datafusion/core/benches/sort.rs +++ b/datafusion/core/benches/sort.rs @@ -67,6 +67,7 @@ //! ``` use std::sync::Arc; +use std::time::Duration; use arrow::array::{ArrayRef, StringViewArray, StringViewBuilder}; use arrow::{ @@ -163,62 +164,62 @@ fn criterion_benchmark(c: &mut Criterion) { "i64", Box::new(move |sorted| i64_streams(sorted, input_size)), ), - // ( - // "f64", - // Box::new(move |sorted| f64_streams(sorted, input_size)), - // ), - // ( - // "utf8 low cardinality", - // Box::new(move |sorted| utf8_low_cardinality_streams(sorted, input_size)), - // ), - // ( - // "utf8 high cardinality", - // Box::new(move |sorted| utf8_high_cardinality_streams(sorted, input_size)), - // ), - // ( - // "utf8 view low cardinality", - // Box::new(move |sorted| { - // utf8_view_low_cardinality_streams(sorted, input_size) - // }), - // ), - // ( - // "utf8 view high cardinality", - // Box::new(move |sorted| { - // utf8_view_high_cardinality_streams(sorted, input_size) - // }), - // ), - // ( - // "utf8 tuple", - // Box::new(move |sorted| utf8_tuple_streams(sorted, input_size)), - // ), - // ( - // "utf8 view tuple", - // Box::new(move |sorted| utf8_view_tuple_streams(sorted, input_size)), - // ), - // ( - // "utf8 dictionary", - // Box::new(move |sorted| dictionary_streams(sorted, input_size)), - // ), - // ( - // "utf8 dictionary tuple", - // Box::new(move |sorted| dictionary_tuple_streams(sorted, input_size)), - // ), - // ( - // "mixed dictionary tuple", - // Box::new(move |sorted| { - // mixed_dictionary_tuple_streams(sorted, input_size) - // }), - // ), - // ( - // "mixed tuple", - // Box::new(move |sorted| mixed_tuple_streams(sorted, input_size)), - // ), - // ( - // "mixed tuple with utf8 view", - // Box::new(move |sorted| { - // mixed_tuple_with_utf8_view_streams(sorted, input_size) - // }), - // ), + ( + "f64", + Box::new(move |sorted| f64_streams(sorted, input_size)), + ), + ( + "utf8 low cardinality", + Box::new(move |sorted| utf8_low_cardinality_streams(sorted, input_size)), + ), + ( + "utf8 high cardinality", + Box::new(move |sorted| utf8_high_cardinality_streams(sorted, input_size)), + ), + ( + "utf8 view low cardinality", + Box::new(move |sorted| { + utf8_view_low_cardinality_streams(sorted, input_size) + }), + ), + ( + "utf8 view high cardinality", + Box::new(move |sorted| { + utf8_view_high_cardinality_streams(sorted, input_size) + }), + ), + ( + "utf8 tuple", + Box::new(move |sorted| utf8_tuple_streams(sorted, input_size)), + ), + ( + "utf8 view tuple", + Box::new(move |sorted| utf8_view_tuple_streams(sorted, input_size)), + ), + ( + "utf8 dictionary", + Box::new(move |sorted| dictionary_streams(sorted, input_size)), + ), + ( + "utf8 dictionary tuple", + Box::new(move |sorted| dictionary_tuple_streams(sorted, input_size)), + ), + ( + "mixed dictionary tuple", + Box::new(move |sorted| { + mixed_dictionary_tuple_streams(sorted, input_size) + }), + ), + ( + "mixed tuple", + Box::new(move |sorted| mixed_tuple_streams(sorted, input_size)), + ), + ( + "mixed tuple with utf8 view", + Box::new(move |sorted| { + mixed_tuple_with_utf8_view_streams(sorted, input_size) + }), + ), ]; for (name, f) in &cases { @@ -823,34 +824,68 @@ type AxisGenerator = Box PartitionedB /// copy while reordering and more memory to hold /// 3. Value cardinality - whether the sort-key values overlap or not /// 4. Input ordering - already sorted / unsorted / nearly sorted -fn sort_axis_benchmark(c: &mut Criterion) { - let input_size = 1_000_000u64; - let size_label = "1M"; +/// Input size for the axis benchmark at a given extra-column count. The +/// wide-payload cases (20+ extra cols) are dominated by payload reordering, so +/// 100k rows still exercises the wide-take path without the multi-second-per-iter +/// cost of 1M. +fn axis_input_size(extra: usize) -> (u64, &'static str) { + if extra >= 20 { + (100_000, "100k") + } else { + (1_000_000, "1M") + } +} +/// Read a duration (seconds, may be fractional) from `var`. Falls back to +/// `default_secs` when unset; panics if set to a value that isn't a number. +fn env_duration(var: &str, default_secs: f64) -> Duration { + let secs = match std::env::var(var) { + Ok(s) => s + .parse::() + .unwrap_or_else(|e| panic!("invalid {var}={s:?}: {e}")), + Err(_) => default_secs, + }; + Duration::from_secs_f64(secs) +} + +fn sort_axis_benchmark(c: &mut Criterion) { let cases: Vec<(&str, AxisGenerator)> = vec![ ( "i64", - Box::new(move |p, card, extra| i64_axis(p, card, extra, input_size)), + Box::new(|p, card, extra| i64_axis(p, card, extra, axis_input_size(extra).0)), ), ( "utf8 view", - Box::new(move |p, card, extra| utf8_view_axis(p, card, extra, input_size)), + Box::new(|p, card, extra| { + utf8_view_axis(p, card, extra, axis_input_size(extra).0) + }), ), ( "mixed tuple", - Box::new(move |p, card, extra| mixed_tuple_axis(p, card, extra, input_size)), + Box::new(|p, card, extra| { + mixed_tuple_axis(p, card, extra, axis_input_size(extra).0) + }), ), ]; + // The axis benches are all floor-bound (short per-iter), so the default + // criterion warm-up/measurement windows dominate their wall-clock. Shrink + // them here; override with SORT_AXIS_WARMUP_SECS / SORT_AXIS_MEASUREMENT_SECS. + let mut group = c.benchmark_group("sort_axis"); + group.sample_size(10); + group.warm_up_time(env_duration("SORT_AXIS_WARMUP_SECS", 1.0)); + group.measurement_time(env_duration("SORT_AXIS_MEASUREMENT_SECS", 2.0)); + for (name, f) in &cases { for card in [Cardinality::Low, Cardinality::High] { for &extra in EXTRA_COLUMN_COUNTS { + let size_label = axis_input_size(extra).1; for profile in [ DataProfile::Sorted, DataProfile::Unsorted, DataProfile::NearlySorted, ] { - c.bench_function( + group.bench_function( &format!( "sort {name} {size_label} {card:?} cardinality {profile:?} +{extra}cols", ), @@ -864,6 +899,7 @@ fn sort_axis_benchmark(c: &mut Criterion) { } } } + group.finish(); } /// Single-column i64 batches @@ -989,5 +1025,5 @@ fn with_extra_columns(batches: PartitionedBatches, n: usize) -> PartitionedBatch .collect() } -criterion_group!(benches, criterion_benchmark); +criterion_group!(benches, criterion_benchmark, sort_axis_benchmark); criterion_main!(benches); From 75f955cef1e0fb034715fc059b9e2718f47ecc52 Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Tue, 14 Jul 2026 18:08:46 +0300 Subject: [PATCH 4/4] reduce --- datafusion/core/benches/sort.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/datafusion/core/benches/sort.rs b/datafusion/core/benches/sort.rs index 51edd97a3966c..4f062bae559af 100644 --- a/datafusion/core/benches/sort.rs +++ b/datafusion/core/benches/sort.rs @@ -848,6 +848,17 @@ fn env_duration(var: &str, default_secs: f64) -> Duration { Duration::from_secs_f64(secs) } +/// Read a `usize` from `var`. Falls back to `default` when unset; panics if set +/// to a value that isn't an integer. +fn env_usize(var: &str, default: usize) -> usize { + match std::env::var(var) { + Ok(s) => s + .parse::() + .unwrap_or_else(|e| panic!("invalid {var}={s:?}: {e}")), + Err(_) => default, + } +} + fn sort_axis_benchmark(c: &mut Criterion) { let cases: Vec<(&str, AxisGenerator)> = vec![ ( @@ -868,11 +879,13 @@ fn sort_axis_benchmark(c: &mut Criterion) { ), ]; - // The axis benches are all floor-bound (short per-iter), so the default - // criterion warm-up/measurement windows dominate their wall-clock. Shrink - // them here; override with SORT_AXIS_WARMUP_SECS / SORT_AXIS_MEASUREMENT_SECS. let mut group = c.benchmark_group("sort_axis"); - group.sample_size(10); + // These benches measure whole-plan sort execution, whose run-to-run variance + // is low (~1%), so a small sample count is enough to catch a regression while + // avoiding 100 samples of a multi-hundred-ms plan. 10 is criterion's minimum. + group.sample_size(env_usize("SORT_AXIS_SAMPLE_SIZE", 10)); + // Every axis bench is floor-bound (short per-iter), so criterion's default + // warm-up/measurement windows dominate their wall-clock; shrink them here. group.warm_up_time(env_duration("SORT_AXIS_WARMUP_SECS", 1.0)); group.measurement_time(env_duration("SORT_AXIS_MEASUREMENT_SECS", 2.0));