2121//! Motivated by <https://github.com/apache/datafusion/issues/17850> which
2222//! showed vectorized can regress for low-cardinality, high-row-count scenarios.
2323//!
24- //! Uses the direct `GroupValues::intern()` API with identical Int32 data for
25- //! both implementations — a fair apples-to-apples comparison with the same
26- //! hashing and data layout.
27-
28- use arrow:: array:: { ArrayRef , Int32Array } ;
24+ //! Uses the direct `GroupValues::intern()` API with identical data for both
25+ //! implementations — a fair apples-to-apples comparison with the same hashing
26+ //! and data layout. Most experiments use `Int32` columns; `bench_fixed_size_binary`
27+ //! covers a `(FixedSizeBinary, Int32)` key to exercise the
28+ //! `FixedSizeBinaryGroupValueBuilder`.
29+
30+ use arrow:: array:: { ArrayRef , Int32Array , UInt32Array } ;
31+ use arrow:: compute:: take;
2932use arrow:: datatypes:: { DataType , Field , Schema , SchemaRef } ;
33+ use arrow:: util:: bench_util:: create_fsb_array;
3034use criterion:: { BenchmarkId , Criterion , criterion_group, criterion_main} ;
3135use datafusion_physical_plan:: aggregates:: group_values:: GroupValues ;
3236use datafusion_physical_plan:: aggregates:: group_values:: GroupValuesRows ;
@@ -344,6 +348,102 @@ fn bench_group_count_sweep(c: &mut Criterion) {
344348 group. finish ( ) ;
345349}
346350
351+ /// Width in bytes of the FixedSizeBinary group column (UUID-sized).
352+ const FSB_WIDTH : usize = 16 ;
353+
354+ /// Schema for the FixedSizeBinary experiment: a `FixedSizeBinary` group column
355+ /// paired with an `Int32` column, exercising a multi-column GROUP BY that
356+ /// includes a fixed-width binary key (e.g. grouping on a UUID).
357+ fn make_fsb_schema ( ) -> SchemaRef {
358+ Arc :: new ( Schema :: new ( vec ! [
359+ Field :: new( "fsb" , DataType :: FixedSizeBinary ( FSB_WIDTH as i32 ) , false ) ,
360+ Field :: new( "id" , DataType :: Int32 , false ) ,
361+ ] ) )
362+ }
363+
364+ /// Generate `(FixedSizeBinary, Int32)` batches with exactly
365+ /// `num_distinct_groups` distinct keys.
366+ ///
367+ /// The distinct FixedSizeBinary values come from arrow-rs's `create_fsb_array`
368+ /// benchmark generator; rows cycle through that pool (mirroring how
369+ /// `generate_batches` controls Int32 cardinality) so the group count is
370+ /// controlled. The `Int32` column is keyed identically, keeping the combined
371+ /// cardinality equal to `num_distinct_groups`.
372+ fn generate_fsb_batches (
373+ num_distinct_groups : usize ,
374+ num_rows : usize ,
375+ batch_size : usize ,
376+ ) -> Vec < Vec < ArrayRef > > {
377+ // Pool of distinct FixedSizeBinary values (fixed seed, no nulls).
378+ let pool = create_fsb_array ( num_distinct_groups, 0.0 , FSB_WIDTH ) ;
379+
380+ let num_full_batches = num_rows / batch_size;
381+ let remainder = num_rows % batch_size;
382+ let num_batches = num_full_batches + if remainder > 0 { 1 } else { 0 } ;
383+
384+ ( 0 ..num_batches)
385+ . map ( |batch_idx| {
386+ let batch_start = batch_idx * batch_size;
387+ let current_batch_size = if batch_idx == num_batches - 1 && remainder > 0 {
388+ remainder
389+ } else {
390+ batch_size
391+ } ;
392+
393+ let group_ids = ( 0 ..current_batch_size)
394+ . map ( |row| ( batch_start + row) % num_distinct_groups) ;
395+
396+ let indices: UInt32Array = group_ids. clone ( ) . map ( |g| g as u32 ) . collect ( ) ;
397+ let fsb = take ( & pool, & indices, None ) . unwrap ( ) ;
398+ let id: Int32Array = group_ids. map ( |g| g as i32 ) . collect ( ) ;
399+
400+ vec ! [ fsb, Arc :: new( id) as ArrayRef ]
401+ } )
402+ . collect ( )
403+ }
404+
405+ /// Experiment 7: Group count sweep for a `(FixedSizeBinary, Int32)` key.
406+ ///
407+ /// Exercises the `FixedSizeBinaryGroupValueBuilder` used by multi-column
408+ /// GROUP BY. Before FixedSizeBinary support, such a schema fell back to the
409+ /// row-based `GroupValuesRows`; this compares the vectorized columnar path
410+ /// (`vectorized`) against that baseline (`row_based`).
411+ fn bench_fixed_size_binary ( c : & mut Criterion ) {
412+ let mut group = c. benchmark_group ( "fixed_size_binary" ) ;
413+ group. sample_size ( 15 ) ;
414+
415+ let schema = make_fsb_schema ( ) ;
416+
417+ for num_groups in [ 1_000 , 1_000_000 ] {
418+ let batches = generate_fsb_batches ( num_groups, 1_000_000 , DEFAULT_BATCH_SIZE ) ;
419+
420+ for vectorized in [ true , false ] {
421+ let label = if vectorized {
422+ "vectorized"
423+ } else {
424+ "row_based"
425+ } ;
426+ group. bench_with_input (
427+ BenchmarkId :: new ( label, format ! ( "grp_{num_groups}" ) ) ,
428+ & batches,
429+ |b, batches| {
430+ b. iter_batched_ref (
431+ || {
432+ (
433+ create_group_values ( & schema, vectorized) ,
434+ Vec :: < usize > :: with_capacity ( DEFAULT_BATCH_SIZE ) ,
435+ )
436+ } ,
437+ |( gv, groups) | bench_intern ( gv, batches, groups) ,
438+ criterion:: BatchSize :: LargeInput ,
439+ ) ;
440+ } ,
441+ ) ;
442+ }
443+ }
444+ group. finish ( ) ;
445+ }
446+
347447criterion_group ! (
348448 benches,
349449 bench_issue_17850_regression,
@@ -352,5 +452,6 @@ criterion_group!(
352452 bench_column_scaling,
353453 bench_high_cardinality_scaling,
354454 bench_group_count_sweep,
455+ bench_fixed_size_binary,
355456) ;
356457criterion_main ! ( benches) ;
0 commit comments