Skip to content

Commit 4957f5d

Browse files
alambclaude
andauthored
bench: add FixedSizeBinary coverage to multi_group_by benchmark (#23650)
## Which issue does this PR close? - Related to #23646 - Related to #23645. ## Rationale for this change The point of a specialized FixedSizeBinary group values is performance but we have no performance benchmark for it. ## What changes are included in this PR? Adds a `fixed_size_binary` experiment to `datafusion/physical-plan/benches/multi_group_by.rs` Run with: ```bash cargo bench -p datafusion-physical-plan --bench multi_group_by --features test_utils -- fixed_size_binary ``` ## Are these changes tested? This is benchmark-only. The benchmark compiles on `main` and runs end-to-end on top of #23646. No product code changes. ## Are there any user-facing changes? No. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ca02890 commit 4957f5d

2 files changed

Lines changed: 107 additions & 5 deletions

File tree

datafusion/physical-plan/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,4 @@ required-features = ["test_utils"]
142142
[[bench]]
143143
harness = false
144144
name = "multi_group_by"
145+
required-features = ["test_utils"]

datafusion/physical-plan/benches/multi_group_by.rs

Lines changed: 106 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,16 @@
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;
2932
use arrow::datatypes::{DataType, Field, Schema, SchemaRef};
33+
use arrow::util::bench_util::create_fsb_array;
3034
use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
3135
use datafusion_physical_plan::aggregates::group_values::GroupValues;
3236
use 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+
347447
criterion_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
);
356457
criterion_main!(benches);

0 commit comments

Comments
 (0)