Repository navigation
perf: unroll PrimitiveArray hashing for the no-null path - #26191
Rich-T-kid wants to merge 2 commits into
Conversation
Splits the single scalar loop in `hash_array_primitive`'s no-null branch
into two const-generic-free helpers that batch independent hashes so the
pipeline isn't serialized on store-to-load deps:
- `hash_prim_fresh_dense`: overwrite mode, 16 rows per iteration.
- `hash_prim_rehash_dense`: fold-with-prev mode, 8 rows per iteration
(narrower because each lane holds both `prev` and `value` live).
Both use `get_unchecked` / `get_unchecked_mut` with proven invariants
(hashes.len() == values.len()) and a scalar tail for the remainder.
Benchmarks (int64, 8192 rows, no nulls) on Apple M4 Max:
single, no nulls : neutral to small regression (<+7%)
multiple, no nulls : ~-14%
The nullable path is unchanged -- see PR apache#26143 for the sibling change.
|
run benchmark with_hashes |
5027475 to
4d6ff20
Compare
|
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing rich-T-kid/primitive-hash-no-null-unroll (4d6ff20) to 1c49b7f (merge-base) diff Run configurationrun benchmark with_hashesResults will be posted here when complete File an issue against this benchmark runner |
|
run benchmark with_hashes |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #26191 +/- ##
==========================================
+ Coverage 82.78% 82.80% +0.02%
==========================================
Files 1148 1149 +1
Lines 451049 452284 +1235
Branches 451049 452284 +1235
==========================================
+ Hits 373381 374533 +1152
- Misses 54928 54953 +25
- Partials 22740 22798 +58 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
🤖 Benchmark completed (GKE) | trigger Instance: Comparing rich-T-kid/primitive-hash-no-null-unroll (4d6ff20) to 1c49b7f (merge-base) diff Run configurationrun benchmark with_hashesCPU Details (lscpu)Details
Resource Usagewith_hashes — base (merge-base)
with_hashes — branch
File an issue against this benchmark runner |
|
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing rich-T-kid/primitive-hash-no-null-unroll (4d6ff20) to 1c49b7f (merge-base) diff Run configurationrun benchmark with_hashesResults will be posted here when complete File an issue against this benchmark runner |
|
🤖 Benchmark completed (GKE) | trigger Instance: Comparing rich-T-kid/primitive-hash-no-null-unroll (4d6ff20) to 1c49b7f (merge-base) diff Run configurationrun benchmark with_hashesCPU Details (lscpu)Details
Resource Usagewith_hashes — base (merge-base)
with_hashes — branch
File an issue against this benchmark runner |
|
run benchmark with_hashes |
1 similar comment
|
run benchmark with_hashes |
|
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing rich-T-kid/primitive-hash-no-null-unroll (da7240f) to 1c49b7f (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-no-null-unroll (da7240f) to 1c49b7f (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-no-null-unroll (da7240f) to 1c49b7f (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-no-null-unroll (da7240f) to 1c49b7f (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 |
Which issue does this PR close?
Rationale for this change
The no-null branch of
hash_array_primitiveis a tightzipoverhashes_bufferandarray.values(). ForPrimitiveArray<Int64>and friendsthe whole hash is a handful of cycles, so the serialization imposed by one
scalar iteration per row shows up as a measurable per-batch cost.
What changes are included in this PR?
Replace the two scalar loops with two small helpers that batch independent
hashes before storing:
hash_prim_fresh_dense: single-column case (rehash == false), 16 rowsper iteration. Each lane only reads
values[row], so lane count can gowide before register pressure matters.
hash_prim_rehash_dense: multi-column case (rehash == true), 8 rowsper iteration. Each lane holds both
prev_hashandvaluelive;widening past 8 spilled registers in the multi-column case.
Both use
std::array::from_fn::<LANES>to gather + hash into a temporaryarray, then store the whole batch, keeps the independent hash chains in
flight so the pipeline isn't serialized on store-to-load deps. A scalar
tail handles rows beyond the last
LANES-aligned boundary.What is the testing strategy for this PR?
Existing
hash_utils::testsAre there any user-facing changes?
no