From a5444d2330424f563277f492a56e96a4dbf6ca8e Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Tue, 4 Aug 2026 13:40:24 -0500 Subject: [PATCH] bench(parquet): cover DELTA_BYTE_ARRAY at sub-page-limit value sizes The existing delta benches use 2 MiB values against the default 1 MiB page limit, where every value is cut onto its own page and prefix state is reset each time -- DELTA_BYTE_ARRAY output there is byte-identical to PLAIN, so the shared-prefix scan never runs to depth. Add 1 KiB values, which sit far below the limit and share a page, across full / partial / no shared prefix. --- parquet/benches/arrow_writer.rs | 79 ++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/parquet/benches/arrow_writer.rs b/parquet/benches/arrow_writer.rs index c8dd7b8479de..f7376b944dcc 100644 --- a/parquet/benches/arrow_writer.rs +++ b/parquet/benches/arrow_writer.rs @@ -148,6 +148,23 @@ fn create_large_string_distinct_bench_batch(size: usize, value_size: usize) -> R Ok(RecordBatch::try_from_iter([("col", array)])?) } +/// `size` rows of `value_size`-byte strings sharing their first +/// `shared_bytes` bytes and differing thereafter — the realistic sorted-column +/// case (paths, URLs, keys), where prefix deduplication saves part of each +/// value rather than all or none of it. +fn create_string_partial_prefix_bench_batch( + size: usize, + value_size: usize, + shared_bytes: usize, +) -> Result { + let shared = "x".repeat(shared_bytes); + let tail = "y".repeat(value_size - shared_bytes - 8); + let array = Arc::new(StringArray::from_iter_values( + (0..size).map(|i| format!("{shared}{i:08}{tail}")), + )) as _; + Ok(RecordBatch::try_from_iter([("col", array)])?) +} + fn create_string_and_binary_view_bench_batch( size: usize, null_density: f32, @@ -676,6 +693,61 @@ fn bench_all_writers(c: &mut Criterion) { } } +/// Writes BYTE_ARRAY columns of *small* string values with `DELTA_BYTE_ARRAY`, +/// with `PLAIN` on the same data as a baseline. +/// +/// Values here sit far below `data_page_size_limit`, so many share a page and +/// the encoder's previous-value state survives across them. This is the regime +/// `DELTA_BYTE_ARRAY` is actually deployed in, and — unlike the multi-MiB +/// benches below — the one where the shared-prefix scan runs to real depth. +/// +/// * `small_string_shared_prefix`: values differing only in a trailing counter, +/// so each scan covers nearly the whole value. +/// * `small_string_partial_prefix`: values sharing their first half, the +/// sorted-column case. +/// * `small_string_distinct`: values differing from byte 0, where the scan +/// stops immediately and prefix deduplication saves nothing. +fn bench_small_delta_byte_array_writers(c: &mut Criterion) { + const ROWS: usize = 8192; + const VALUE_SIZE: usize = 1024; + + let shared_prefix = create_large_string_shared_prefix_bench_batch(ROWS, VALUE_SIZE).unwrap(); + let partial_prefix = + create_string_partial_prefix_bench_batch(ROWS, VALUE_SIZE, VALUE_SIZE / 2).unwrap(); + let distinct = create_large_string_distinct_bench_batch(ROWS, VALUE_SIZE).unwrap(); + + let plain = WriterProperties::builder() + .set_dictionary_enabled(false) + .set_encoding(Encoding::PLAIN) + .build(); + let delta = WriterProperties::builder() + .set_dictionary_enabled(false) + .set_encoding(Encoding::DELTA_BYTE_ARRAY) + .build(); + + for (batch_name, batch) in [ + ("small_string_shared_prefix", &shared_prefix), + ("small_string_partial_prefix", &partial_prefix), + ("small_string_distinct", &distinct), + ] { + let mut group = c.benchmark_group(batch_name); + group.throughput(Throughput::Bytes( + batch + .columns() + .iter() + .map(|f| f.get_array_memory_size() as u64) + .sum(), + )); + + for (prop_name, prop) in [("plain", &plain), ("delta_byte_array", &delta)] { + group.bench_function(prop_name, |b| { + write_batch_with_option(b, batch, Some((*prop).clone())).unwrap() + }); + } + group.finish(); + } +} + /// Writes BYTE_ARRAY columns of large (multi-MiB) string values with /// `DELTA_BYTE_ARRAY`, with `PLAIN` on the same data as a baseline. /// @@ -723,5 +795,10 @@ fn bench_delta_byte_array_writers(c: &mut Criterion) { } } -criterion_group!(benches, bench_all_writers, bench_delta_byte_array_writers); +criterion_group!( + benches, + bench_all_writers, + bench_small_delta_byte_array_writers, + bench_delta_byte_array_writers +); criterion_main!(benches);