Reuse hash values during Aggregate(partial) -> Repartition -> Aggregate(final)#23720
Reuse hash values during Aggregate(partial) -> Repartition -> Aggregate(final)#23720gabotechs wants to merge 6 commits into
Conversation
|
run benchmarks tpch10 |
|
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing gabotechs/reuse-hashes (45fa16b) to d0304b3 (merge-base) diff using: tpch10 File an issue against this benchmark runner |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #23720 +/- ##
==========================================
+ Coverage 80.71% 80.79% +0.07%
==========================================
Files 1089 1090 +1
Lines 368750 370024 +1274
Branches 368750 370024 +1274
==========================================
+ Hits 297631 298951 +1320
+ Misses 53375 53337 -38
+ Partials 17744 17736 -8 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
🤖 Benchmark completed (GKE) | trigger Instance: CPU Details (lscpu)Details
Resource Usagetpch10 — base (merge-base)
tpch10 — branch
File an issue against this benchmark runner |
|
run benchmarks clickbench_partitioned |
|
Thank you for opening this pull request! Reviewer note: cargo-semver-checks reported the current version number is not SemVer-compatible with the changes in this pull request (compared against the base branch). Details |
|
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing gabotechs/reuse-hashes (45fa16b) to d0304b3 (merge-base) diff using: clickbench_partitioned File an issue against this benchmark runner |
|
🤖 Benchmark completed (GKE) | trigger Instance: CPU Details (lscpu)Details
Resource Usageclickbench_partitioned — base (merge-base)
clickbench_partitioned — branch
File an issue against this benchmark runner |
|
run benchmarks tpcds |
|
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing gabotechs/reuse-hashes (45fa16b) to d0304b3 (merge-base) diff using: tpcds File an issue against this benchmark runner |
|
🤖 Benchmark completed (GKE) | trigger Instance: CPU Details (lscpu)Details
Resource Usagetpcds — base (merge-base)
tpcds — branch
File an issue against this benchmark runner |
1158083 to
1c84f94
Compare
|
At a highlevel, this change sounds good. It would be interesting to see if the partial aggregation skipping behavior can be disabled with no impact. I think this should do it. Since the hashes are re-used the partial aggregation should be less expensive. |
|
Yeah, I can imagine how you'd want to enable this based on the hashing expressions. For example, maybe hashing on a simple I still want to see if we can reach a situation where this enabled 100% of the times is worth it, that would simplify things a bit. |
I think for primitives I found it can be a quite slower to store the hash rather than regenerate it inside a loop as hashing is just is a shift / multiply / xor (potentially SIMD) vs a potential cache miss / extra cache usage. For (long) strings it can of course be very beneficial. |
Agree, what is worse: the internal hash column will be repartitioned and coalesced together with other normal columns, and
Seems strange that q33 and q34 not get improvement... |
|
run benchmarks tpch10 tpcds clickbench_partitioned |
|
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing gabotechs/reuse-hashes (7d64c1d) to d0304b3 (merge-base) diff using: tpch10 File an issue against this benchmark runner |
|
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing gabotechs/reuse-hashes (7d64c1d) to d0304b3 (merge-base) diff using: tpcds File an issue against this benchmark runner |
|
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing gabotechs/reuse-hashes (7d64c1d) to d0304b3 (merge-base) diff using: clickbench_partitioned File an issue against this benchmark runner |
|
🤖 Benchmark completed (GKE) | trigger Instance: CPU Details (lscpu)Details
Resource Usagetpch10 — base (merge-base)
tpch10 — branch
File an issue against this benchmark runner |
|
🤖 Benchmark completed (GKE) | trigger Instance: CPU Details (lscpu)Details
Resource Usagetpcds — base (merge-base)
tpcds — branch
File an issue against this benchmark runner |
|
🤖 Benchmark completed (GKE) | trigger Instance: CPU Details (lscpu)Details
Resource Usageclickbench_partitioned — base (merge-base)
clickbench_partitioned — branch
File an issue against this benchmark runner |
7d64c1d to
cbfb9cb
Compare
|
I'm looking at ClickBench Q33, that should show some improvement, and even though hashes are getting reused: $ SELECT "URL", COUNT(*) AS c FROM hits GROUP BY "URL" ORDER BY c DESC LIMIT 10;
SortPreservingMergeExec: [c@1 DESC], fetch=10, metrics=[]
ProjectionExec: expr=[URL@0 as URL, count(Int64(1))@1 as c], metrics=[]
SortExec: TopK(fetch=10), expr=[count(Int64(1))@1 DESC], preserve_partitioning=[true], filter=[count(Int64(1))@1 IS NULL OR count(Int64(1))@1 > 55255], metrics=[]
AggregateExec: mode=FinalPartitioned, gby=[URL@0 as URL], aggr=[count(Int64(1))], metrics=[hash_rows_computed=0, hash_rows_reused=22.98 M]
RepartitionExec: partitioning=Hash([URL@0], 16), input_partitions=16, metrics=[hash_rows_computed=0, hash_rows_reused=22.98 M]
AggregateExec: mode=Partial, gby=[URL@0 as URL], aggr=[count(Int64(1))], metrics=[hash_rows_computed=100.00 M, hash_rows_reused=0]
DataSourceExec: ...It seems to have 0 impact on performance: Doing some profiling of this branch vs Here are the two profiles for ClickBench Q33:
|
|
run benchmarks clickbench_partitioned |
bf28ff4 to
9bcc60a
Compare
|
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing gabotechs/reuse-hashes (bf28ff4) to d0304b3 (merge-base) diff using: clickbench_partitioned File an issue against this benchmark runner |
|
🤖 Benchmark completed (GKE) | trigger Instance: CPU Details (lscpu)Details
Resource Usageclickbench_partitioned — base (merge-base)
clickbench_partitioned — branch
File an issue against this benchmark runner |
|
run benchmarks clickbench_partitioned |
|
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing gabotechs/reuse-hashes (9bcc60a) to 050046a (merge-base) diff using: clickbench_partitioned File an issue against this benchmark runner |
Still in draft 🙈, expect heavy changes
Which issue does this PR close?
Rationale for this change
During partial + final aggregations, in order to accumulate + repartition data cross partitions, a hash is computed over the grouping expressions.
This hash is computed 3 times:
And depending on what's getting hashed, it can be an expensive operation (e.g., hashing arbitrary length strings).
This PR introduces a mechanism that allows reusing the hashes across different operators, by propagating them under an internal column in the schema:
What changes are included in this PR?
Introduces to core structs that aim to set the foundations of hash reuse across operators:
ExpressionHasher: handles hashing columns based on expressions, maintaining an internal buffer for reusing allocations, stamping the hashes into internal columns so that they can cross operator boundaries, and retrieving those hashes from the internal columns if present.GroupHashTracker: during groupings, it keeps track of the per-group hashes so that operators that aggregate can emit them as an additional internal column for reuse purposes.These two structs are used across all the aggregation implementations in the codebase for reusing hashes.
Additionally, some metrics for tracking how many hashes where computed VS how many hashes where reused are also added to operators that need to deal with hashes
Are these changes tested?
Yes, by existing and new tests.
Are there any user-facing changes?
No, this should be transparent to users.