This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-11639-4c8d7086dd4ec5aa97520a3a1a8cb7a866b31f34 in repository https://gitbox.apache.org/repos/asf/arrow-rs.git
commit 781baa42796c5b25c98fa8f0fc7379c16c782df2 Author: Huaijin <[email protected]> AuthorDate: Sat Oct 17 08:48:38 2026 +0000 perf(parquet): trim bitmap-backed row selections using 64-bit words (#11639) # Which issue does this PR close? Closes #11640 # Rationale for this change Trimming a bitmap-backed row selection scans trailing unset bits byte by byte. Long tails of skipped rows add unnecessary selection setup overhead. # What changes are included in this PR? Scan backwards through 64-bit words using `BitChunks`, checking the masked remainder first. Use `leading_zeros` to locate the final selected row. This reuses existing handling of bit offsets and padding while preserving the fast path for selections that need no trimming. The implementation was prepared with AI assistance. # Are these changes tested? Validated with the existing randomized trim equivalence tests and cursor tests covering word boundaries, long zero tails, non-zero bit offsets, and set bits outside the logical slice. All 89 selection tests pass. # Are there any significant user-facing changes? No. --- .../src/arrow/arrow_reader/selection/boolean.rs | 25 +++++++++------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/parquet/src/arrow/arrow_reader/selection/boolean.rs b/parquet/src/arrow/arrow_reader/selection/boolean.rs index e5b698533a..ca21ffe0f4 100644 --- a/parquet/src/arrow/arrow_reader/selection/boolean.rs +++ b/parquet/src/arrow/arrow_reader/selection/boolean.rs @@ -250,22 +250,17 @@ pub(super) fn split_off_mask( (head, tail) } -/// Position of the highest set bit in `mask`, scanning bytes from the end. +/// Position of the highest set bit in `mask`, scanning words from the end. fn last_set_bit_position(mask: &BooleanBuffer) -> Option<usize> { - let values = mask.values(); - let offset = mask.offset(); - let end = offset + mask.len(); - for byte_idx in (offset / 8..end.div_ceil(8)).rev() { - let byte_start = byte_idx * 8; - let mut byte = values[byte_idx]; - if end - byte_start < 8 { - byte &= (1u8 << (end - byte_start)) - 1; - } - if byte_start < offset { - byte &= !((1u8 << (offset - byte_start)) - 1); - } - if byte != 0 { - return Some(byte_start + 7 - byte.leading_zeros() as usize - offset); + let chunks = mask.bit_chunks(); + let remainder = chunks.remainder_bits(); + if remainder != 0 { + return Some(chunks.chunk_len() * 64 + 63 - remainder.leading_zeros() as usize); + } + for i in (0..chunks.chunk_len()).rev() { + let word = chunks.chunk(i); + if word != 0 { + return Some(i * 64 + 63 - word.leading_zeros() as usize); } } None
