Skip to content

perf: speed up PrimitiveArray hashing for nullable inputs - #26194

Closed
Rich-T-kid wants to merge 2 commits into
apache:mainfrom
Rich-T-kid:rich-T-kid/primitive-hash-null-chunks
Closed

Rich-T-kid wants to merge 2 commits into
apache:mainfrom
Rich-T-kid:rich-T-kid/primitive-hash-null-chunks

Conversation

@Rich-T-kid

@Rich-T-kid Rich-T-kid commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Rationale for this change

Hashing a nullable PrimitiveArray previously called nulls().valid_indices() and
hashed one row at a time. We can speed this up

What changes are included in this PR?

Rewrite the nullable branch of hash_array_primitive to walk the null buffer
in 64-row chunks via NullBuffer::inner().bit_chunks():

  • Dense chunks (mask == u64::MAX): hash 8 rows per inner iteration using
    std::array::from_fn::<8> so the independent hashes stay in flight across the
    pipeline before the stores commit.
  • Mixed chunks: bit-walk only the set bits via remaining & (remaining - 1) +
    trailing_zeros, one hash per set bit, no branching on cleared ones.
  • Tail: same bit-walk masked to remainder_len().

Both paths share one hash_prim_one::<_, _, REHASH> helper, so the
rehash=true (multi-column) and rehash=false (single-column) variants
collapse to one body with the const-generic picking the hash expression.

The no-null branches are intentionally left alone in this PR ! done in #26191

What is the testing strategy for this PR?

Existing hash_utils::tests

Are there any user-facing changes?

no

Walk the null bitmap in 64-row chunks instead of calling `valid_indices()`
per row. Dense chunks (`mask == u64::MAX`) hash 8 rows at a time with
`std::array::from_fn`, so the independent hashes overlap in the pipeline
instead of serializing on the store back to `hashes`. Mixed chunks
bit-walk only the set bits via `trailing_zeros`, so a valid lane costs
one shift + one ctz instead of a per-row null check and bounds-checked
`hashes_buffer[i]`.

The no-null path (`array.null_count() == 0`) is unchanged.

Benchmarks (`datafusion/common/benches/with_hashes.rs`, Apple M4 Max,
int64 at 3% null density, cool-cache A/B in one session):

  single, nulls   : 5.99 µs -> 5.33 µs  (-12%)
  multiple, nulls : 17.44 µs -> 15.51 µs (-11%)

No-null benches stay within noise. The dense-path 8-wide unroll for the
no-null case is deferred to a follow-up PR.
@github-actions github-actions Bot added the common Related to common crate label Oct 11, 2026
@codecov-commenter

codecov-commenter commented Oct 11, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 82.80%. Comparing base (409e3b8) to head (25fdea2).
⚠️ Report is 28 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #26194      +/-   ##
==========================================
+ Coverage   82.75%   82.80%   +0.05%     
==========================================
  Files        1147     1149       +2     
  Lines      449886   452300    +2414     
  Branches   449886   452300    +2414     
==========================================
+ Hits       372303   374545    +2242     
- Misses      54911    54957      +46     
- Partials    22672    22798     +126     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Rich-T-kid

Copy link
Copy Markdown
Contributor Author

run benchmark with_hashes
env:
BENCH_FILTER: int64

1 similar comment
@Rich-T-kid

Copy link
Copy Markdown
Contributor Author

run benchmark with_hashes
env:
BENCH_FILTER: int64

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c6117240677-3313-82r9r 6.12.94+ #1 SMP Fri Aug 21 08:00:16 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/primitive-hash-null-chunks (25fdea2) to 409e3b8 (merge-base) diff

Run configuration
run benchmark with_hashes
env:
  BENCH_FILTER: "int64"

Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c6117241062-3314-fc5dz 6.12.94+ #1 SMP Fri Aug 21 08:00:16 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/primitive-hash-null-chunks (25fdea2) to 409e3b8 (merge-base) diff

Run configuration
run benchmark with_hashes
env:
  BENCH_FILTER: "int64"

Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark completed (GKE) | trigger

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

Comparing rich-T-kid/primitive-hash-null-chunks (25fdea2) to 409e3b8 (merge-base) diff

Run configuration
run benchmark with_hashes
env:
  BENCH_FILTER: "int64"
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                                        HEAD                                   rich-T-kid_primitive-hash-null-chunks
-----                                        ----                                   -------------------------------------
int64: multiple, no nulls                    1.00     17.0±0.01µs        ? ?/sec    1.00     16.9±0.01µs        ? ?/sec
int64: multiple, nulls                       1.05     28.7±0.03µs        ? ?/sec    1.00     27.3±0.02µs        ? ?/sec
int64: single, no nulls                      1.00      4.6±0.02µs        ? ?/sec    1.00      4.6±0.01µs        ? ?/sec
int64: single, nulls                         1.00      8.7±0.01µs        ? ?/sec    1.07      9.3±0.01µs        ? ?/sec
mixed: 3 columns (int64, utf8, utf8_view)    1.00     81.1±1.47µs        ? ?/sec    1.01     81.9±0.29µs        ? ?/sec

Resource Usage

with_hashes — base (merge-base)

Metric Value
Wall time 95.0s
Peak memory 36.2 MiB
Avg memory 14.8 MiB
CPU user 61.4s
CPU sys 0.1s
Peak spill 0 B

with_hashes — branch

Metric Value
Wall time 110.0s
Peak memory 39.4 MiB
Avg memory 13.8 MiB
CPU user 59.3s
CPU sys 0.0s
Peak spill 0 B

File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark completed (GKE) | trigger

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

Comparing rich-T-kid/primitive-hash-null-chunks (25fdea2) to 409e3b8 (merge-base) diff

Run configuration
run benchmark with_hashes
env:
  BENCH_FILTER: "int64"
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                                        HEAD                                   rich-T-kid_primitive-hash-null-chunks
-----                                        ----                                   -------------------------------------
int64: multiple, no nulls                    1.00     17.0±0.01µs        ? ?/sec    1.00     16.9±0.01µs        ? ?/sec
int64: multiple, nulls                       1.05     28.7±0.02µs        ? ?/sec    1.00     27.4±0.02µs        ? ?/sec
int64: single, no nulls                      1.00      4.6±0.01µs        ? ?/sec    1.00      4.6±0.01µs        ? ?/sec
int64: single, nulls                         1.00      8.7±0.01µs        ? ?/sec    1.07      9.4±0.01µs        ? ?/sec
mixed: 3 columns (int64, utf8, utf8_view)    1.04     82.4±0.24µs        ? ?/sec    1.00     79.0±0.22µs        ? ?/sec

Resource Usage

with_hashes — base (merge-base)

Metric Value
Wall time 100.0s
Peak memory 39.7 MiB
Avg memory 14.7 MiB
CPU user 58.9s
CPU sys 0.1s
Peak spill 0 B

with_hashes — branch

Metric Value
Wall time 90.0s
Peak memory 34.8 MiB
Avg memory 15.5 MiB
CPU user 59.5s
CPU sys 0.0s
Peak spill 0 B

File an issue against this benchmark runner

@Rich-T-kid

Copy link
Copy Markdown
Contributor Author

results are not significant, closing the PR

@Rich-T-kid Rich-T-kid closed this Oct 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

common Related to common crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants