Skip to content

perf(filter): replace bit-at-a-time null bitmap filtering - #11055

Open
Rich-T-kid wants to merge 5 commits into
apache:mainfrom
Rich-T-kid:rich-t-kid/filter-bits-gather-optimization
Open

Rich-T-kid wants to merge 5 commits into
apache:mainfrom
Rich-T-kid:rich-t-kid/filter-bits-gather-optimization

Conversation

@Rich-T-kid

@Rich-T-kid Rich-T-kid commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Rationale for this change

When filtering arrays with a null bitmap, the current code walks one bit at a time

What changes are included in this PR?

Replaces the bit-at-a-time null bitmap loop with a word-level approach: instead of reading one bit per selected row, we load 64 filter bits and 64 source bits at a time, extract only the bits where the filter is set, and pack them directly into the output buffer.

Are these changes tested?

yes, existing test

Are there any user-facing changes?

no

…evel PEXT

The IndexIterator/Indices paths in filter_bits walked the precomputed
indices Vec (up to 256 KB at 50% selectivity) to read the null bitmap
one byte at a time via get_bit_raw. This caused severe cache pressure
against the values buffer being filtered simultaneously.

Replace with gather_bits: zip 64-bit chunks from the filter and source
bitmaps, apply software PEXT to extract selected bits per chunk. This
works directly on the two 8 KB bitmaps, eliminating the indices Vec
traversal and reducing source reads from O(count) byte loads to
O(filter_len/64) u64 loads.

A threshold (count * 64 < filter_len) keeps the original indices path
for very sparse selections where the tiny precomputed Vec is cheaper
to walk than scanning all filter chunks.

Benchmark delta on existing NULLs benchmarks (65536 elements):
  i32 w NULLs kept 1/2:               149.9 µs -> 38.1 µs  (-74%)
  i32 w NULLs high selectivity:        17.9 µs ->  6.6 µs  (-63%)
  i32 w NULLs low selectivity:          499 ns ->  276 ns  (-60%)
  u8  w NULLs kept 1/2:               159.0 µs -> 63.4 µs  (-75%)
  u8  w NULLs high selectivity:            --  ->  2.8 µs  (-58%)
  u8  w NULLs low selectivity:             --  ->  234 ns  (-35%)
@github-actions github-actions Bot added arrow Changes to the arrow crate arrow-select labels Sep 10, 2026
@Rich-T-kid

Copy link
Copy Markdown
Contributor Author

run benchmark filter_kernels
env:
BENCH_FILTER: NULL

@adriangbot

Copy link
Copy Markdown

🤖 Arrow criterion benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c5624007162-2312-89jl4 6.12.94+ #1 SMP Tue Aug 4 08:44:15 UTC 2026 aarch64 GNU/Linux

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected

Comparing rich-t-kid/filter-bits-gather-optimization (75281be) to 2078680 (merge-base) diff

Run configuration
run benchmark filter_kernels
env:
  BENCH_FILTER: "NULL"

BENCH_COMMAND=cargo bench --features=arrow,async,test_common,experimental,object_store --bench filter_kernels
Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Arrow criterion benchmark completed (GKE) | trigger

Instance: c4a-highmem-16 (12 vCPU / 65 GiB)

Comparing rich-t-kid/filter-bits-gather-optimization (75281be) to 2078680 (merge-base) diff

Run configuration
run benchmark filter_kernels
env:
  BENCH_FILTER: "NULL"
CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected
Details

group                                                                         main                                   rich-t-kid_filter-bits-gather-optimization
-----                                                                         ----                                   ------------------------------------------
filter context i32 w NULLs (kept 1/2)                                         1.39     81.5±0.06µs        ? ?/sec    1.00     58.7±0.04µs        ? ?/sec
filter context i32 w NULLs high selectivity (kept 1023/1024)                  1.00      5.6±0.01µs        ? ?/sec    1.00      5.6±0.01µs        ? ?/sec
filter context i32 w NULLs low selectivity (kept 1/1024)                      1.01    321.5±0.97ns        ? ?/sec    1.00    316.8±1.26ns        ? ?/sec
filter context string dictionary w NULLs (kept 1/2)                           1.40     81.6±0.05µs        ? ?/sec    1.00     58.1±0.05µs        ? ?/sec
filter context string dictionary w NULLs high selectivity (kept 1023/1024)    1.02      5.8±0.01µs        ? ?/sec    1.00      5.6±0.01µs        ? ?/sec
filter context string dictionary w NULLs low selectivity (kept 1/1024)        1.00    376.7±2.31ns        ? ?/sec    1.02    382.5±2.52ns        ? ?/sec
filter context u8 w NULLs (kept 1/2)                                          1.40     81.5±0.19µs        ? ?/sec    1.00     58.1±0.05µs        ? ?/sec
filter context u8 w NULLs high selectivity (kept 1023/1024)                   1.00      2.8±0.01µs        ? ?/sec    1.00      2.8±0.01µs        ? ?/sec
filter context u8 w NULLs low selectivity (kept 1/1024)                       1.00    306.2±1.66ns        ? ?/sec    1.02    313.5±3.56ns        ? ?/sec

Resource Usage

base (merge-base)

Metric Value
Wall time 95.0s
Peak memory 23.1 MiB
Avg memory 14.0 MiB
CPU user 91.8s
CPU sys 0.0s
Peak spill 0 B

branch

Metric Value
Wall time 95.0s
Peak memory 22.5 MiB
Avg memory 13.7 MiB
CPU user 89.1s
CPU sys 0.0s
Peak spill 0 B

File an issue against this benchmark runner

@Rich-T-kid Rich-T-kid left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trim

Comment thread arrow-select/src/filter.rs Outdated
Comment thread arrow-select/src/filter.rs Outdated
@Rich-T-kid

Copy link
Copy Markdown
Contributor Author

run benchmark filter_kernels
env:
BENCH_FILTER: NULL

1 similar comment
@Rich-T-kid

Copy link
Copy Markdown
Contributor Author

run benchmark filter_kernels
env:
BENCH_FILTER: NULL

@adriangbot

Copy link
Copy Markdown

🤖 Arrow criterion benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c5639714015-2320-55t89 6.12.94+ #1 SMP Tue Aug 4 08:44:15 UTC 2026 aarch64 GNU/Linux

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected

Comparing rich-t-kid/filter-bits-gather-optimization (200598b) to 2078680 (merge-base) diff

Run configuration
run benchmark filter_kernels
env:
  BENCH_FILTER: "NULL"

BENCH_COMMAND=cargo bench --features=arrow,async,test_common,experimental,object_store --bench filter_kernels
Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Arrow criterion benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c5639714741-2321-fh8nd 6.12.94+ #1 SMP Tue Aug 4 08:44:15 UTC 2026 aarch64 GNU/Linux

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected

Comparing rich-t-kid/filter-bits-gather-optimization (200598b) to 2078680 (merge-base) diff

Run configuration
run benchmark filter_kernels
env:
  BENCH_FILTER: "NULL"

BENCH_COMMAND=cargo bench --features=arrow,async,test_common,experimental,object_store --bench filter_kernels
Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Arrow criterion benchmark completed (GKE) | trigger

Instance: c4a-highmem-16 (12 vCPU / 65 GiB)

Comparing rich-t-kid/filter-bits-gather-optimization (200598b) to 2078680 (merge-base) diff

Run configuration
run benchmark filter_kernels
env:
  BENCH_FILTER: "NULL"
CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected
Details

group                                                                         main                                   rich-t-kid_filter-bits-gather-optimization
-----                                                                         ----                                   ------------------------------------------
filter context i32 w NULLs (kept 1/2)                                         1.77     82.2±0.66µs        ? ?/sec    1.00     46.6±0.09µs        ? ?/sec
filter context i32 w NULLs high selectivity (kept 1023/1024)                  1.00      5.6±0.01µs        ? ?/sec    1.00      5.6±0.01µs        ? ?/sec
filter context i32 w NULLs low selectivity (kept 1/1024)                      1.00    321.9±1.51ns        ? ?/sec    1.00    320.9±2.30ns        ? ?/sec
filter context string dictionary w NULLs (kept 1/2)                           1.67     81.5±0.07µs        ? ?/sec    1.00     48.7±0.88µs        ? ?/sec
filter context string dictionary w NULLs high selectivity (kept 1023/1024)    1.00      5.6±0.01µs        ? ?/sec    1.02      5.7±0.01µs        ? ?/sec
filter context string dictionary w NULLs low selectivity (kept 1/1024)        1.00    376.2±2.84ns        ? ?/sec    1.02    382.8±1.67ns        ? ?/sec
filter context u8 w NULLs (kept 1/2)                                          1.71     81.2±0.03µs        ? ?/sec    1.00     47.6±1.32µs        ? ?/sec
filter context u8 w NULLs high selectivity (kept 1023/1024)                   1.01      2.8±0.01µs        ? ?/sec    1.00      2.8±0.01µs        ? ?/sec
filter context u8 w NULLs low selectivity (kept 1/1024)                       1.00    306.7±2.05ns        ? ?/sec    1.02    313.2±2.33ns        ? ?/sec

Resource Usage

base (merge-base)

Metric Value
Wall time 100.0s
Peak memory 23.1 MiB
Avg memory 14.1 MiB
CPU user 94.8s
CPU sys 0.0s
Peak spill 0 B

branch

Metric Value
Wall time 90.0s
Peak memory 22.5 MiB
Avg memory 13.5 MiB
CPU user 84.1s
CPU sys 0.0s
Peak spill 0 B

File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Arrow criterion benchmark completed (GKE) | trigger

Instance: c4a-highmem-16 (12 vCPU / 65 GiB)

Comparing rich-t-kid/filter-bits-gather-optimization (200598b) to 2078680 (merge-base) diff

Run configuration
run benchmark filter_kernels
env:
  BENCH_FILTER: "NULL"
CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected
Details

group                                                                         main                                   rich-t-kid_filter-bits-gather-optimization
-----                                                                         ----                                   ------------------------------------------
filter context i32 w NULLs (kept 1/2)                                         1.70     82.1±1.93µs        ? ?/sec    1.00     48.3±0.36µs        ? ?/sec
filter context i32 w NULLs high selectivity (kept 1023/1024)                  1.00      5.6±0.02µs        ? ?/sec    1.00      5.6±0.01µs        ? ?/sec
filter context i32 w NULLs low selectivity (kept 1/1024)                      1.02    323.8±3.80ns        ? ?/sec    1.00    318.8±0.92ns        ? ?/sec
filter context string dictionary w NULLs (kept 1/2)                           1.79     82.1±0.23µs        ? ?/sec    1.00     45.8±0.14µs        ? ?/sec
filter context string dictionary w NULLs high selectivity (kept 1023/1024)    1.00      5.6±0.01µs        ? ?/sec    1.00      5.6±0.01µs        ? ?/sec
filter context string dictionary w NULLs low selectivity (kept 1/1024)        1.00    376.2±2.63ns        ? ?/sec    1.03    387.4±3.06ns        ? ?/sec
filter context u8 w NULLs (kept 1/2)                                          1.75     81.3±0.37µs        ? ?/sec    1.00     46.4±0.23µs        ? ?/sec
filter context u8 w NULLs high selectivity (kept 1023/1024)                   1.00      2.8±0.02µs        ? ?/sec    1.00      2.8±0.01µs        ? ?/sec
filter context u8 w NULLs low selectivity (kept 1/1024)                       1.00    308.7±5.47ns        ? ?/sec    1.01    312.9±4.28ns        ? ?/sec

Resource Usage

base (merge-base)

Metric Value
Wall time 100.0s
Peak memory 23.1 MiB
Avg memory 14.0 MiB
CPU user 94.8s
CPU sys 0.0s
Peak spill 0 B

branch

Metric Value
Wall time 90.0s
Peak memory 22.5 MiB
Avg memory 14.2 MiB
CPU user 87.1s
CPU sys 0.0s
Peak spill 0 B

File an issue against this benchmark runner

@Rich-T-kid
Rich-T-kid force-pushed the rich-t-kid/filter-bits-gather-optimization branch from c275451 to 4727e6a Compare September 11, 2026 19:54
@Rich-T-kid

Copy link
Copy Markdown
Contributor Author

run benchmark filter_kernels
env:
BENCH_FILTER: NULL

1 similar comment
@Rich-T-kid

Copy link
Copy Markdown
Contributor Author

run benchmark filter_kernels
env:
BENCH_FILTER: NULL

@Rich-T-kid
Rich-T-kid marked this pull request as ready for review September 11, 2026 19:55
@Rich-T-kid

Copy link
Copy Markdown
Contributor Author

@sdf-jkl could you take a look 🚀

@Rich-T-kid Rich-T-kid changed the title WIP perf(filter): replace bit-at-a-time null bitmap filtering perf(filter): replace bit-at-a-time null bitmap filtering Sep 11, 2026
@adriangbot

Copy link
Copy Markdown

🤖 Arrow criterion benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c5639890125-2322-crmbd 6.12.94+ #1 SMP Tue Aug 4 08:44:15 UTC 2026 aarch64 GNU/Linux

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected

Comparing rich-t-kid/filter-bits-gather-optimization (4727e6a) to 2078680 (merge-base) diff

Run configuration
run benchmark filter_kernels
env:
  BENCH_FILTER: "NULL"

BENCH_COMMAND=cargo bench --features=arrow,async,test_common,experimental,object_store --bench filter_kernels
Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Arrow criterion benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c5639890747-2323-rrvcc 6.12.94+ #1 SMP Tue Aug 4 08:44:15 UTC 2026 aarch64 GNU/Linux

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected

Comparing rich-t-kid/filter-bits-gather-optimization (4727e6a) to 2078680 (merge-base) diff

Run configuration
run benchmark filter_kernels
env:
  BENCH_FILTER: "NULL"

BENCH_COMMAND=cargo bench --features=arrow,async,test_common,experimental,object_store --bench filter_kernels
Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Arrow criterion benchmark completed (GKE) | trigger

Instance: c4a-highmem-16 (12 vCPU / 65 GiB)

Comparing rich-t-kid/filter-bits-gather-optimization (4727e6a) to 2078680 (merge-base) diff

Run configuration
run benchmark filter_kernels
env:
  BENCH_FILTER: "NULL"
CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected
Details

group                                                                         main                                   rich-t-kid_filter-bits-gather-optimization
-----                                                                         ----                                   ------------------------------------------
filter context i32 w NULLs (kept 1/2)                                         1.73     81.4±0.07µs        ? ?/sec    1.00     47.0±0.62µs        ? ?/sec
filter context i32 w NULLs high selectivity (kept 1023/1024)                  1.00      5.6±0.01µs        ? ?/sec    1.00      5.6±0.02µs        ? ?/sec
filter context i32 w NULLs low selectivity (kept 1/1024)                      1.01    323.2±1.53ns        ? ?/sec    1.00    320.8±6.27ns        ? ?/sec
filter context string dictionary w NULLs (kept 1/2)                           1.69     81.6±0.12µs        ? ?/sec    1.00     48.2±0.15µs        ? ?/sec
filter context string dictionary w NULLs high selectivity (kept 1023/1024)    1.00      5.6±0.01µs        ? ?/sec    1.00      5.6±0.01µs        ? ?/sec
filter context string dictionary w NULLs low selectivity (kept 1/1024)        1.01    382.0±2.70ns        ? ?/sec    1.00    377.3±2.04ns        ? ?/sec
filter context u8 w NULLs (kept 1/2)                                          1.75     81.3±0.15µs        ? ?/sec    1.00     46.3±0.14µs        ? ?/sec
filter context u8 w NULLs high selectivity (kept 1023/1024)                   1.00      2.8±0.01µs        ? ?/sec    1.01      2.9±0.02µs        ? ?/sec
filter context u8 w NULLs low selectivity (kept 1/1024)                       1.00    308.4±2.22ns        ? ?/sec    1.00    308.1±2.60ns        ? ?/sec

Resource Usage

base (merge-base)

Metric Value
Wall time 100.0s
Peak memory 23.1 MiB
Avg memory 14.2 MiB
CPU user 95.6s
CPU sys 0.0s
Peak spill 0 B

branch

Metric Value
Wall time 90.0s
Peak memory 22.5 MiB
Avg memory 14.3 MiB
CPU user 87.1s
CPU sys 0.0s
Peak spill 0 B

File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Arrow criterion benchmark completed (GKE) | trigger

Instance: c4a-highmem-16 (12 vCPU / 65 GiB)

Comparing rich-t-kid/filter-bits-gather-optimization (4727e6a) to 2078680 (merge-base) diff

Run configuration
run benchmark filter_kernels
env:
  BENCH_FILTER: "NULL"
CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected
Details

group                                                                         main                                   rich-t-kid_filter-bits-gather-optimization
-----                                                                         ----                                   ------------------------------------------
filter context i32 w NULLs (kept 1/2)                                         1.76     81.6±0.62µs        ? ?/sec    1.00     46.5±0.15µs        ? ?/sec
filter context i32 w NULLs high selectivity (kept 1023/1024)                  1.00      5.6±0.02µs        ? ?/sec    1.00      5.5±0.01µs        ? ?/sec
filter context i32 w NULLs low selectivity (kept 1/1024)                      1.01    319.5±1.19ns        ? ?/sec    1.00    316.3±0.85ns        ? ?/sec
filter context string dictionary w NULLs (kept 1/2)                           1.76     81.5±0.05µs        ? ?/sec    1.00     46.4±0.17µs        ? ?/sec
filter context string dictionary w NULLs high selectivity (kept 1023/1024)    1.00      5.7±0.01µs        ? ?/sec    1.02      5.7±0.01µs        ? ?/sec
filter context string dictionary w NULLs low selectivity (kept 1/1024)        1.00    377.6±1.91ns        ? ?/sec    1.01    381.2±1.61ns        ? ?/sec
filter context u8 w NULLs (kept 1/2)                                          1.75     81.2±0.04µs        ? ?/sec    1.00     46.4±0.55µs        ? ?/sec
filter context u8 w NULLs high selectivity (kept 1023/1024)                   1.00      2.8±0.01µs        ? ?/sec    1.00      2.8±0.01µs        ? ?/sec
filter context u8 w NULLs low selectivity (kept 1/1024)                       1.00    306.4±1.73ns        ? ?/sec    1.00    308.0±2.56ns        ? ?/sec

Resource Usage

base (merge-base)

Metric Value
Wall time 100.0s
Peak memory 23.1 MiB
Avg memory 14.0 MiB
CPU user 94.7s
CPU sys 0.0s
Peak spill 0 B

branch

Metric Value
Wall time 90.0s
Peak memory 22.5 MiB
Avg memory 13.5 MiB
CPU user 84.1s
CPU sys 0.0s
Peak spill 0 B

File an issue against this benchmark runner

Comment on lines -697 to -701
// SAFETY: indices were derived from the filter predicate
let bits = indices.iter().map(|src_idx| unsafe {
bit_util::get_bit_raw(buffer.values().as_ptr(), *src_idx + offset)
});
// SAFETY: `Vec::iter()` reports its size correctly

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

going to add these safety comments back in

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah defiitely important to add back in

@sdf-jkl

sdf-jkl commented Sep 11, 2026

Copy link
Copy Markdown
Member

I'll take a look this weekend

@sdf-jkl sdf-jkl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Rich-T-kid looking good

Comment thread arrow-select/src/filter.rs Outdated

/// Collects the bits of `val` wherever `mask` is 1, packed into the low bits of the result.
#[inline(always)]
fn pext64(val: u64, mut mask: u64) -> u64 {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There already is an implementation of PEXT in the codebase --

pub(crate) fn compress(value: u64, mask: u64) -> u64 {
#[cfg(all(target_arch = "x86_64", target_feature = "bmi2"))]
{
// SAFETY: the `bmi2` target feature is statically enabled for this
// build, so the `pext` instruction is guaranteed to be available.
unsafe { std::arch::x86_64::_pext_u64(value, mask) }
}
#[cfg(not(all(target_arch = "x86_64", target_feature = "bmi2")))]
{
let mut mask = mask;
let mut result: u64 = 0;
let mut dest_bit: u64 = 1;
while mask != 0 {
let lowest = mask & mask.wrapping_neg();
if value & lowest != 0 {
result |= dest_bit;
}
dest_bit <<= 1;
mask ^= lowest;
}
result
}
}
#[cfg(test)]

Yours seems to perform better though (on my machine 🤓 ) If anything we can drop drop the parquet one and make it import your implementation.

Rust has a nightly impl-- doc.rust-lang.org/std/primitive.u64.html#method.extract_bits

The issue here - rust-lang/rust#149069 - explains that the implementation is waiting on supporting the new LLVM intrinsics that support automatically using the PEXT/PDEP machine instructions if machine supports them. When the instructions are available the perf is pretty epic.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea I think moving this out so it can be re-used is a good idea. Would be nice to see how much of a speed up parquet gets from this as well

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arrow-buffer/src/util/bit_util.rs is where @devanbenz put it

}
};

for (filter_word, src_word) in filter_chunks.iter().zip(src_chunks.iter()) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Processing each word is independent from each other so this could be paralellizable.

I killed some time on it today. You can take a look here -- #11086

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a great follow on task perhaps

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, can open up a follow on PR

@Rich-T-kid

Copy link
Copy Markdown
Contributor Author

@sdf-jkl your PR seems to be faster than this one, should I close this in favor of that one?

@sdf-jkl

sdf-jkl commented Sep 15, 2026

Copy link
Copy Markdown
Member

Mine was just pathfinding and only better when SIMD enabled, not on scalar. You can port smth / take inspiration from my PR here.

@alamb alamb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very neat -- thank you @Rich-T-kid @sdf-jkl and @devanbenz

It will be pretty amazing to get 75% faster on some filter kernels 🤯

I'll keep an eye on this one

FYI @jhorstmann and @hhhizzz you may be interested too

Comment thread arrow-select/src/filter.rs Outdated
}
};

for (filter_word, src_word) in filter_chunks.iter().zip(src_chunks.iter()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a great follow on task perhaps

Comment thread arrow-select/src/filter.rs Outdated

/// Collects the bits of `val` wherever `mask` is 1, packed into the low bits of the result.
#[inline(always)]
fn pext64(val: u64, mut mask: u64) -> u64 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arrow-buffer/src/util/bit_util.rs is where @devanbenz put it

@alamb alamb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it might also make sense to ensure the benchmarks cover this case as well (clearly some do but maybe not all)

Comment on lines -697 to -701
// SAFETY: indices were derived from the filter predicate
let bits = indices.iter().map(|src_idx| unsafe {
bit_util::get_bit_raw(buffer.values().as_ptr(), *src_idx + offset)
});
// SAFETY: `Vec::iter()` reports its size correctly

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah defiitely important to add back in

@github-actions github-actions Bot added parquet Changes to the parquet crate arrow-buffer labels Sep 15, 2026
@Rich-T-kid

Copy link
Copy Markdown
Contributor Author

@alamb @sdf-jkl @devanbenz could you take another look

@Rich-T-kid
Rich-T-kid force-pushed the rich-t-kid/filter-bits-gather-optimization branch from f83ffde to fa5badb Compare September 15, 2026 20:37
@Rich-T-kid
Rich-T-kid force-pushed the rich-t-kid/filter-bits-gather-optimization branch from fa5badb to 9f60687 Compare September 15, 2026 20:38
@devanbenz

devanbenz commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Hi @Rich-T-kid and @alamb this seems very similar to the PR I have here: https://github.com/apache/arrow-rs/pull/10136/changes 🤔

If we are planning on going with your implementation here it may be a good idea to pull over some of the benchmarking I did in mine and verify that sparse and dense bitmaps are accounted for -- specifically using PEXT, I had to run the benchmarks on my local x86 machine since the GHA CI running CPU is ARM based. I was finding performance issues using pext when a bitmap was either of the two. It wasn't much of a performance hit just a few percentage points, but still.

@Rich-T-kid

Copy link
Copy Markdown
Contributor Author

@devanbenz our PR's are very similar but I think the focus of both a separate. this PR was meant to be a more narrowed optimization.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arrow Changes to the arrow crate arrow-buffer arrow-select parquet Changes to the parquet crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

replace bit-at-a-time null bitmap filtering with word-level

5 participants