bharadwaj-pendyala opened a new pull request, #11257:
URL: https://github.com/apache/arrow-rs/pull/11257

   # Which issue does this PR close?
   
   - Closes #11200.
   
   # Rationale for this change
   
   When the input has nulls, `FilterPredicate::filter_nulls` always filters the 
validity into a new buffer and counts it, then drops the buffer if the count 
comes out zero. A filter that keeps no null rows, like `x IS NOT NULL AND ...`, 
pays for a bitmap it never uses.
   
   # What changes are included in this PR?
   
   `filter_nulls` now asks `selects_null` first and returns `None` straight 
away when no selected row is null.
   
   `selects_null` zips the 64-bit words of the mask and the validity and stops 
at the first word where `keep & !valid` is non-zero. It doesn't allocate, and 
it takes the offsets of both buffers into account. For an optimized `Indices` 
predicate below one selected row per word, it probes those indices instead. 
That's the crossover `filter_bits` already uses for `sparse_indices`. Without 
it, a 1/2048 filter scans all 1024 words to check 32 rows, and above that 
crossover the word scan wins. An early version probed indices for every 
`Indices` predicate and took 98 µs at kept 1/4 instead of 43 µs.
   
   `selects_null` also asserts the null buffer covers the filter. Before, a 
too-short buffer panicked inside `filter_bits`. Without the assert, the sparse 
index path would read only the selected rows and quietly return `None`.
   
   # Are these changes tested?
   
   Yes, in `arrow-select/src/filter.rs`:
   
   - `test_filter_nulls_selecting_only_valid_rows` asserts `selects_null` is 
false and the filtered array has no null buffer.
   - `test_filter_nulls_selecting_one_null` asserts it's true when the only 
selected null is row 0 (a full word) or row 129 (the partial last word).
   
   Both run the lazy and optimized predicates, sliced and unsliced masks, over 
values sliced at offset 1. The null patterns put the masks on both sides of the 
slices threshold. `test_filter_nulls_shorter_than_filter` covers the assert.
   
   Each of these edits turns at least one test red: dropping the trailing 
partial word, ignoring the null buffer's offset, ignoring the mask's offset, 
having the index probe return false, having `selects_null` return true (today's 
behaviour), and removing the assert.
   
   `cargo test -p arrow-select` is 447 passed, 0 failed. `cargo test -p parquet 
--features arrow --lib arrow::arrow_reader` is 145 passed. `cargo fmt --all -- 
--check` and `cargo clippy -p arrow-select --all-targets --all-features -- -D 
warnings` are clean.
   
   Benchmarks are the cases from #11256. I built two release binaries, main at 
`fa337f8` and this branch, and ran them alternately. M1 laptop, criterion 
median, 3 rounds unless noted:
   
   ```
                                                             main            
this PR
   i32 w NULLs, only valid (kept 1/4)                        73-135 µs       
42-43 µs
   i32 w NULLs, only valid high sel. (kept 1023/2048)        147-222 µs      
73-74 µs
   i32 w NULLs, only valid low sel. (kept 1/2048)            1.10-1.13 µs    
0.63-0.66 µs
   i32 w NULLs at end (kept 1/2)                             219-222 µs      
223-226 µs
   i32 w NULLs at end high sel. (kept 1023/1024), 8 rounds   ~42 µs          
~45 µs
   i32 w NULLs (kept 1/2), existing                          221-230 µs      
221-223 µs
   i32 w NULLs high sel. (kept 1023/1024), existing         43-51 µs        43 
µs
   ```
   
   The last two nulls-at-end rows are the cost. When the only selected null 
sits in the last word, the scan reads both bitmaps once before `filter_bits` 
reads them again, which comes to about 7% on the dense case. With random nulls 
the scan stops in the first word, and the existing cases don't move beyond 
noise. Main swings a lot on the only-valid rows between runs, and this branch 
doesn't, so the low end of the main range is the fair comparison.
   
   # Are there any user-facing changes?
   
   No API change. `filter_nulls` returns the same thing as before. A null 
buffer shorter than the filter that has any nulls still panics, now with 
`assertion failed: nulls.len() >= len` whatever the strategy.
   
   # AI usage
   
   Claude wrote the change, tests and benchmarks. Codex reviewed the diff 
adversarially twice before the push. The first pass flagged the index probe 
running for dense `Indices` predicates and the missing length check. The second 
pass flagged that the only-valid test would pass even if `selects_null` always 
returned true. I fixed all three and ran the edits above to check the tests 
catch them. I also measured every number here myself.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to