Repository navigation
perf: speed up PrimitiveArray hashing for nullable inputs - #26194
Rich-T-kid wants to merge 2 commits into
Conversation
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.
Codecov Report✅ All modified and coverable lines are covered by tests. 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. 🚀 New features to boost your workflow:
|
|
run benchmark with_hashes |
1 similar comment
|
run benchmark with_hashes |
|
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing rich-T-kid/primitive-hash-null-chunks (25fdea2) to 409e3b8 (merge-base) diff Run configurationrun benchmark with_hashes
env:
BENCH_FILTER: "int64"Results will be posted here when complete File an issue against this benchmark runner |
|
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing rich-T-kid/primitive-hash-null-chunks (25fdea2) to 409e3b8 (merge-base) diff Run configurationrun benchmark with_hashes
env:
BENCH_FILTER: "int64"Results will be posted here when complete File an issue against this benchmark runner |
|
🤖 Benchmark completed (GKE) | trigger Instance: Comparing rich-T-kid/primitive-hash-null-chunks (25fdea2) to 409e3b8 (merge-base) diff Run configurationrun benchmark with_hashes
env:
BENCH_FILTER: "int64"CPU Details (lscpu)Details
Resource Usagewith_hashes — base (merge-base)
with_hashes — branch
File an issue against this benchmark runner |
|
🤖 Benchmark completed (GKE) | trigger Instance: Comparing rich-T-kid/primitive-hash-null-chunks (25fdea2) to 409e3b8 (merge-base) diff Run configurationrun benchmark with_hashes
env:
BENCH_FILTER: "int64"CPU Details (lscpu)Details
Resource Usagewith_hashes — base (merge-base)
with_hashes — branch
File an issue against this benchmark runner |
|
results are not significant, closing the PR |
Which issue does this PR close?
Rationale for this change
Hashing a nullable
PrimitiveArraypreviously callednulls().valid_indices()andhashed one row at a time. We can speed this up
What changes are included in this PR?
Rewrite the nullable branch of
hash_array_primitiveto walk the null bufferin 64-row chunks via
NullBuffer::inner().bit_chunks():mask == u64::MAX): hash 8 rows per inner iteration usingstd::array::from_fn::<8>so the independent hashes stay in flight across thepipeline before the stores commit.
remaining & (remaining - 1)+trailing_zeros, one hash per set bit, no branching on cleared ones.remainder_len().Both paths share one
hash_prim_one::<_, _, REHASH>helper, so therehash=true(multi-column) andrehash=false(single-column) variantscollapse 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::testsAre there any user-facing changes?
no