This is an automated email from the ASF dual-hosted git repository.

etseidl pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git


The following commit(s) were added to refs/heads/main by this push:
     new 5f38aa53d2 benchmarks: ndv benchmarks (#11162)
5f38aa53d2 is described below

commit 5f38aa53d2e5c64df3cfe2718d431010432baea4
Author: RIchard Baah <[email protected]>
AuthorDate: Tue Sep 22 18:59:47 2026 -0400

    benchmarks: ndv benchmarks (#11162)
    
    # Which issue does this PR close?
    
    - Closes #10804..
    
    # Rationale for this change
    
    set_write_row_group_number_distinct_values was added in #10654 with no
    benchmarks. This PR adds them so the cost of NDV tracking is visible and
    regressions are catchable going forward.
    
    # What changes are included in this PR?
    
    Adds a `bench_ndv` benchmark group to `parquet/benches/arrow_writer.rs`
    covering the data types that NDV tracking supports: Int32 (primitive),
    Boolean, Utf8, LargeBinary, and FixedSizeBinary(16).
    
    Each group writes 10 batches of 65,536 rows through a single ArrowWriter
    with `set_write_row_group_number_distinct_values(true)`. Two cardinality
    shapes are exercised per type: low (100 distinct values, cycling) and
    high (all unique, random), since hash-set behaviour differs meaningfully
    between the two.
    
    # Are these changes tested?
    n/a
    
    # Are there any user-facing changes?
    n/a
    cargo bench --bench arrow_writer --features arrow/test_utils -- "ndv/"
---
 parquet/benches/arrow_writer.rs | 220 +++++++++++++++++++++++++++++++++++++++-
 1 file changed, 217 insertions(+), 3 deletions(-)

diff --git a/parquet/benches/arrow_writer.rs b/parquet/benches/arrow_writer.rs
index 55bd8e9802..54fdd27397 100644
--- a/parquet/benches/arrow_writer.rs
+++ b/parquet/benches/arrow_writer.rs
@@ -31,8 +31,8 @@ use std::sync::Arc;
 use arrow::datatypes::*;
 use arrow::util::bench_util::{create_f16_array, create_f32_array, 
create_f64_array};
 use arrow::{record_batch::RecordBatch, util::data_gen::*};
-use arrow_array::builder::{ListBuilder, StringBuilder};
-use arrow_array::{RecordBatchOptions, StringArray};
+use arrow_array::builder::{FixedSizeBinaryBuilder, ListBuilder, StringBuilder};
+use arrow_array::{Int32Array, LargeBinaryArray, RecordBatchOptions, 
StringArray, StringViewArray};
 use parquet::errors::Result;
 use parquet::file::properties::{CdcOptions, WriterProperties, WriterVersion};
 
@@ -1005,10 +1005,224 @@ fn bench_delta_byte_array_writers(c: &mut Criterion) {
     }
 }
 
+const NDV_BATCH_SIZE: usize = 65_536;
+const NDV_NUM_BATCHES: usize = 10;
+const NDV_LOW_CARDINALITY: usize = 100;
+
+// Generates NDV_NUM_BATCHES random batches for high-cardinality cases.
+fn ndv_random_batches(schema: Arc<Schema>) -> Vec<RecordBatch> {
+    (0..NDV_NUM_BATCHES)
+        .map(|_| create_random_batch(schema.clone(), NDV_BATCH_SIZE, 0.0, 
0.5).unwrap())
+        .collect()
+}
+
+// Low-cardinality helpers: cycle through NDV_LOW_CARDINALITY distinct values.
+fn make_int32_batches_low() -> Vec<RecordBatch> {
+    let schema = Arc::new(Schema::new(vec![Field::new(
+        "value",
+        DataType::Int32,
+        false,
+    )]));
+    (0..NDV_NUM_BATCHES)
+        .map(|batch_idx| {
+            let values: Vec<i32> = (0..NDV_BATCH_SIZE)
+                .map(|row| ((batch_idx * NDV_BATCH_SIZE + row) % 
NDV_LOW_CARDINALITY) as i32)
+                .collect();
+            RecordBatch::try_new(
+                schema.clone(),
+                vec![Arc::new(Int32Array::from(values)) as _],
+            )
+            .unwrap()
+        })
+        .collect()
+}
+
+fn make_utf8_batches_low() -> Vec<RecordBatch> {
+    let schema = Arc::new(Schema::new(vec![Field::new(
+        "label",
+        DataType::Utf8,
+        false,
+    )]));
+    (0..NDV_NUM_BATCHES)
+        .map(|batch_idx| {
+            let values: Vec<String> = (0..NDV_BATCH_SIZE)
+                .map(|row| {
+                    format!(
+                        "label_{}",
+                        (batch_idx * NDV_BATCH_SIZE + row) % 
NDV_LOW_CARDINALITY
+                    )
+                })
+                .collect();
+            let array = StringArray::from(values.iter().map(|s| 
s.as_str()).collect::<Vec<_>>());
+            RecordBatch::try_new(schema.clone(), vec![Arc::new(array) as 
_]).unwrap()
+        })
+        .collect()
+}
+
+fn make_large_binary_batches_low() -> Vec<RecordBatch> {
+    let schema = Arc::new(Schema::new(vec![Field::new(
+        "payload",
+        DataType::LargeBinary,
+        false,
+    )]));
+    (0..NDV_NUM_BATCHES)
+        .map(|batch_idx| {
+            let payloads: Vec<Vec<u8>> = (0..NDV_BATCH_SIZE)
+                .map(|row| {
+                    format!(
+                        "payload_{}",
+                        (batch_idx * NDV_BATCH_SIZE + row) % 
NDV_LOW_CARDINALITY
+                    )
+                    .into_bytes()
+                })
+                .collect();
+            let refs: Vec<&[u8]> = payloads.iter().map(|p| 
p.as_slice()).collect();
+            RecordBatch::try_new(
+                schema.clone(),
+                vec![Arc::new(LargeBinaryArray::from(refs)) as _],
+            )
+            .unwrap()
+        })
+        .collect()
+}
+
+// Utf8View is not yet tracked by NDV (skipped in 
update_distinct_values_seen), so these
+// benchmarks measure base write cost only — useful as a no-overhead baseline.
+fn make_utf8_view_batches_low() -> Vec<RecordBatch> {
+    let schema = Arc::new(Schema::new(vec![Field::new(
+        "label",
+        DataType::Utf8View,
+        false,
+    )]));
+    (0..NDV_NUM_BATCHES)
+        .map(|batch_idx| {
+            let values: Vec<String> = (0..NDV_BATCH_SIZE)
+                .map(|row| {
+                    format!(
+                        "label_{}",
+                        (batch_idx * NDV_BATCH_SIZE + row) % 
NDV_LOW_CARDINALITY
+                    )
+                })
+                .collect();
+            let array = 
StringViewArray::from_iter_values(values.iter().map(|s| s.as_str()));
+            RecordBatch::try_new(schema.clone(), vec![Arc::new(array) as 
_]).unwrap()
+        })
+        .collect()
+}
+
+fn make_fsb_batches_low(byte_width: i32) -> Vec<RecordBatch> {
+    let schema = Arc::new(Schema::new(vec![Field::new(
+        "id",
+        DataType::FixedSizeBinary(byte_width),
+        false,
+    )]));
+    let width = byte_width as usize;
+    (0..NDV_NUM_BATCHES)
+        .map(|batch_idx| {
+            let mut builder = FixedSizeBinaryBuilder::new(byte_width);
+            for row in 0..NDV_BATCH_SIZE {
+                let distinct_id = (batch_idx * NDV_BATCH_SIZE + row) % 
NDV_LOW_CARDINALITY;
+                let mut buf = vec![0u8; width];
+                let encoded = distinct_id.to_le_bytes();
+                buf[..encoded.len().min(width)]
+                    .copy_from_slice(&encoded[..encoded.len().min(width)]);
+                builder.append_value(&buf).unwrap();
+            }
+            RecordBatch::try_new(schema.clone(), 
vec![Arc::new(builder.finish()) as _]).unwrap()
+        })
+        .collect()
+}
+
+fn write_ndv_batches(b: &mut criterion::Bencher, batches: &[RecordBatch]) {
+    let props = WriterProperties::builder()
+        .set_write_row_group_number_distinct_values(true)
+        .build();
+    let schema = batches[0].schema();
+    b.iter(|| {
+        let mut file = Empty::default();
+        let mut writer =
+            ArrowWriter::try_new(&mut file, schema.clone(), 
Some(props.clone())).unwrap();
+        for batch in batches {
+            writer.write(black_box(batch)).unwrap();
+        }
+        black_box(writer.close()).unwrap();
+    });
+}
+
+fn bench_ndv(c: &mut Criterion) {
+    let int32_schema = Arc::new(Schema::new(vec![Field::new(
+        "value",
+        DataType::Int32,
+        false,
+    )]));
+    let bool_schema = Arc::new(Schema::new(vec![Field::new(
+        "flag",
+        DataType::Boolean,
+        false,
+    )]));
+    let utf8_schema = Arc::new(Schema::new(vec![Field::new(
+        "label",
+        DataType::Utf8,
+        false,
+    )]));
+    let large_binary_schema = Arc::new(Schema::new(vec![Field::new(
+        "payload",
+        DataType::LargeBinary,
+        false,
+    )]));
+    let fsb16_schema = Arc::new(Schema::new(vec![Field::new(
+        "id",
+        DataType::FixedSizeBinary(16),
+        false,
+    )]));
+    let utf8_view_schema = Arc::new(Schema::new(vec![Field::new(
+        "label",
+        DataType::Utf8View,
+        false,
+    )]));
+
+    let cases: Vec<(&str, Vec<RecordBatch>)> = vec![
+        ("int32_low_cardinality", make_int32_batches_low()),
+        ("int32_high_cardinality", ndv_random_batches(int32_schema)),
+        ("bool", ndv_random_batches(bool_schema)),
+        ("utf8_low_cardinality", make_utf8_batches_low()),
+        ("utf8_high_cardinality", ndv_random_batches(utf8_schema)),
+        (
+            "large_binary_low_cardinality",
+            make_large_binary_batches_low(),
+        ),
+        (
+            "large_binary_high_cardinality",
+            ndv_random_batches(large_binary_schema),
+        ),
+        ("fsb16_low_cardinality", make_fsb_batches_low(16)),
+        ("fsb16_high_cardinality", ndv_random_batches(fsb16_schema)),
+        ("utf8_view_low_cardinality", make_utf8_view_batches_low()),
+        (
+            "utf8_view_high_cardinality",
+            ndv_random_batches(utf8_view_schema),
+        ),
+    ];
+
+    for (name, batches) in cases {
+        let total_bytes: u64 = batches
+            .iter()
+            .flat_map(|b| b.columns())
+            .map(|c| c.get_array_memory_size() as u64)
+            .sum();
+
+        let mut group = c.benchmark_group(format!("ndv/{name}"));
+        group.throughput(Throughput::Bytes(total_bytes));
+        group.bench_function("ndv_on", |b| write_ndv_batches(b, &batches));
+        group.finish();
+    }
+}
+
 criterion_group!(
     benches,
     bench_all_writers,
     bench_small_delta_byte_array_writers,
-    bench_delta_byte_array_writers
+    bench_delta_byte_array_writers,
+    bench_ndv,
 );
 criterion_main!(benches);

Reply via email to