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

alamb 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 8c230e6499 Add filter benchmarks for predicates that select no null 
rows (#11256)
8c230e6499 is described below

commit 8c230e6499a7d6405457006f1dcb85ee28b07339
Author: Bharadwaj Pendyala <[email protected]>
AuthorDate: Wed Sep 30 18:55:13 2026 +0000

    Add filter benchmarks for predicates that select no null rows (#11256)
    
    # Which issue does this PR close?
    
    - Part of #11200.
    
    # Rationale for this change
    
    #11200 is about `FilterPredicate::filter_nulls` building and then
    throwing away a validity bitmap when the filter keeps no null rows. None
    of the existing `filter_kernels` cases hit that: every nullable input is
    50% random nulls filtered by a mask that ignores them, so the output
    always has nulls.
    
    CONTRIBUTING asks for new benchmarks in their own PR so the bench runner
    can compare against them, so these come ahead of the kernel change.
    
    # What changes are included in this PR?
    
    Five cases in `arrow/benches/filter_kernels.rs`, next to the existing
    `i32 w NULLs` ones:
    
    - `filter context i32 w NULLs, only valid` at kept 1/4, 1023/2048 and
    1/2048. Each mask is the existing 1/2, dense or sparse mask ANDed with
    `is_not_null` of the data, so it never selects a null.
    - `filter context i32 w NULLs at end` at kept 1/2 and 1023/1024, over an
    array whose only nulls are the last 64 rows. These are the adverse case
    the issue asks for: any check for a selected null has to scan to the
    last word before it finds one.
    
    # Are these changes tested?
    
    Benchmarks only. `cargo clippy -p arrow --bench filter_kernels
    --features test_utils -- -D warnings` is clean and all five run.
    
    Baseline on main at `fa337f8`, M1 laptop, criterion median:
    
    ```
    i32 w NULLs, only valid (kept 1/4)                                ~120 µs
    i32 w NULLs, only valid high selectivity (kept 1023/2048)         ~221 µs
    i32 w NULLs, only valid low selectivity (kept 1/2048)             ~1.10 µs
    i32 w NULLs at end (kept 1/2)                                     ~221 µs
    i32 w NULLs at end high selectivity (kept 1023/1024)              ~42 µs
    ```
    
    This machine is noisy: the same binary moved between 73 µs and 135 µs on
    the first case across runs, so read these as rough.
    
    # Are there any user-facing changes?
    
    No.
    
    # AI usage
    
    Claude wrote the benchmarks and Codex reviewed them adversarially; its
    first pass is what asked for the late-null cases. I ran every number
    above myself.
---
 arrow/benches/filter_kernels.rs | 35 +++++++++++++++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/arrow/benches/filter_kernels.rs b/arrow/benches/filter_kernels.rs
index e8b2495549..8d7a973605 100644
--- a/arrow/benches/filter_kernels.rs
+++ b/arrow/benches/filter_kernels.rs
@@ -22,6 +22,7 @@ use arrow::util::bench_util::*;
 
 use arrow::array::*;
 use arrow::compute::filter;
+use arrow::compute::{and, is_not_null};
 use arrow::datatypes::{Field, Float32Type, Int32Type, Int64Type, Schema, 
UInt8Type};
 
 use arrow_array::types::Decimal128Type;
@@ -115,6 +116,40 @@ fn add_benchmark(c: &mut Criterion) {
         |b| b.iter(|| bench_built_filter(&data_array, &sparse_filter)),
     );
 
+    let valid = is_not_null(&data_array).unwrap();
+    let valid_filter = FilterBuilder::new(&and(&filter_array, &valid).unwrap())
+        .optimize()
+        .build();
+    let valid_dense_filter = FilterBuilder::new(&and(&dense_filter_array, 
&valid).unwrap())
+        .optimize()
+        .build();
+    let valid_sparse_filter = FilterBuilder::new(&and(&sparse_filter_array, 
&valid).unwrap())
+        .optimize()
+        .build();
+    c.bench_function("filter context i32 w NULLs, only valid (kept 1/4)", |b| {
+        b.iter(|| bench_built_filter(&data_array, &valid_filter))
+    });
+    c.bench_function(
+        "filter context i32 w NULLs, only valid high selectivity (kept 
1023/2048)",
+        |b| b.iter(|| bench_built_filter(&data_array, &valid_dense_filter)),
+    );
+    c.bench_function(
+        "filter context i32 w NULLs, only valid low selectivity (kept 1/2048)",
+        |b| b.iter(|| bench_built_filter(&data_array, &valid_sparse_filter)),
+    );
+
+    // Nulls only in the last word, so the filters below find a selected null 
late
+    let data_array: Int32Array = (0..size)
+        .map(|i| (i < size - 64).then_some(i as i32))
+        .collect();
+    c.bench_function("filter context i32 w NULLs at end (kept 1/2)", |b| {
+        b.iter(|| bench_built_filter(&data_array, &filter))
+    });
+    c.bench_function(
+        "filter context i32 w NULLs at end high selectivity (kept 1023/1024)",
+        |b| b.iter(|| bench_built_filter(&data_array, &dense_filter)),
+    );
+
     let data_array = create_primitive_array::<UInt8Type>(size, 0.5);
     c.bench_function("filter context u8 w NULLs (kept 1/2)", |b| {
         b.iter(|| bench_built_filter(&data_array, &filter))

Reply via email to