Repository navigation
perf: stop dictionary work growing with the group count - #26703
Conversation
|
run benchmark clickbench_partitioned |
|
run benchmark with_hashes |
|
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing perf/dictionary-work-bounded-by-rows (bc3d609) to 039c0b9 (merge-base) diff Run configurationrun benchmark clickbench_partitioned
baseline:
env:
DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"
changed:
env:
DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"Results will be posted here when complete File an issue against this benchmark runner |
|
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing perf/dictionary-work-bounded-by-rows (bc3d609) to 039c0b9 (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 perf/dictionary-work-bounded-by-rows (bc3d609) to 039c0b9 (merge-base) diff Run configurationrun benchmark clickbench_partitioned
baseline:
env:
DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"
changed:
env:
DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"CPU Details (lscpu)Details
Resource Usageclickbench_partitioned — base (merge-base)
clickbench_partitioned — branch
File an issue against this benchmark runner |
|
🤖 Benchmark completed (GKE) | trigger Instance: Comparing perf/dictionary-work-bounded-by-rows (bc3d609) to 039c0b9 (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 clickbench_partitioned |
|
run benchmark with_hashes |
|
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing perf/dictionary-work-bounded-by-rows (c459aba) to 039c0b9 (merge-base) diff Run configurationrun benchmark clickbench_partitioned
baseline:
env:
DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"
changed:
env:
DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"Results will be posted here when complete File an issue against this benchmark runner |
|
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing perf/dictionary-work-bounded-by-rows (c459aba) to 039c0b9 (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 perf/dictionary-work-bounded-by-rows (c459aba) to 039c0b9 (merge-base) diff Run configurationrun benchmark clickbench_partitioned
baseline:
env:
DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"
changed:
env:
DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"CPU Details (lscpu)Details
Resource Usageclickbench_partitioned — base (merge-base)
clickbench_partitioned — branch
File an issue against this benchmark runner |
|
🤖 Benchmark completed (GKE) | trigger Instance: Comparing perf/dictionary-work-bounded-by-rows (c459aba) to 039c0b9 (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 |
b41cb27 to
eab0efb
Compare
eab0efb to
cd7235f
Compare
|
run benchmark clickbench_partitioned |
|
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing perf/dictionary-work-bounded-by-rows (cd7235f) to 4bf32cb (merge-base) diff Run configurationrun benchmark clickbench_partitioned
baseline:
env:
DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"
changed:
env:
DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"Results will be posted here when complete File an issue against this benchmark runner |
|
🤖 Benchmark completed (GKE) | trigger Instance: Comparing perf/dictionary-work-bounded-by-rows (cd7235f) to 4bf32cb (merge-base) diff Run configurationrun benchmark clickbench_partitioned
baseline:
env:
DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"
changed:
env:
DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"CPU Details (lscpu)Details
Resource Usageclickbench_partitioned — base (merge-base)
clickbench_partitioned — branch
File an issue against this benchmark runner |
|
run benchmark with_hashes |
|
run benchmark clickbench_partitioned |
|
run benchmark clickbench_partitioned |
|
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing perf/dictionary-work-bounded-by-rows (cd7235f) to 4bf32cb (merge-base) diff Run configurationrun benchmark with_hashesResults will be posted here when complete File an issue against this benchmark runner |
|
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing perf/dictionary-work-bounded-by-rows (cd7235f) to 4bf32cb (merge-base) diff Run configurationrun benchmark clickbench_partitionedResults will be posted here when complete File an issue against this benchmark runner |
|
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing perf/dictionary-work-bounded-by-rows (cd7235f) to 4bf32cb (merge-base) diff Run configurationrun benchmark clickbench_partitioned
env:
DATAFUSION_EXECUTION_TARGET_PARTITIONS: "32"
baseline:
env:
DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"
changed:
env:
DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"Results will be posted here when complete File an issue against this benchmark runner |
|
@Jefffrey thanks for the review. This PR changed substantially after your approval, so here is what's new. Your 8 reviewed commits are unchanged apart from a rebase, and the last 3 commits are new. I plan to merge it; if you'd like to look,
|
|
🤖 Benchmark completed (GKE) | trigger Instance: Comparing perf/dictionary-work-bounded-by-rows (cd7235f) to 4bf32cb (merge-base) diff Run configurationrun benchmark clickbench_partitionedCPU Details (lscpu)Details
Resource Usageclickbench_partitioned — base (merge-base)
clickbench_partitioned — branch
File an issue against this benchmark runner |
|
🤖 Benchmark completed (GKE) | trigger Instance: Comparing perf/dictionary-work-bounded-by-rows (cd7235f) to 4bf32cb (merge-base) diff Run configurationrun benchmark clickbench_partitioned
env:
DATAFUSION_EXECUTION_TARGET_PARTITIONS: "32"
baseline:
env:
DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"
changed:
env:
DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"CPU Details (lscpu)Details
Resource Usageclickbench_partitioned — base (merge-base)
clickbench_partitioned — branch
File an issue against this benchmark runner |
|
🤖 Benchmark completed (GKE) | trigger Instance: Comparing perf/dictionary-work-bounded-by-rows (cd7235f) to 4bf32cb (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 clickbench_partitioned |
|
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing perf/dictionary-work-bounded-by-rows (cd7235f) to 4bf32cb (merge-base) diff Run configurationrun benchmark clickbench_partitionedResults will be posted here when complete File an issue against this benchmark runner |
|
🤖 Benchmark completed (GKE) | trigger Instance: Comparing perf/dictionary-work-bounded-by-rows (cd7235f) to 4bf32cb (merge-base) diff Run configurationrun benchmark clickbench_partitionedCPU Details (lscpu)Details
Resource Usageclickbench_partitioned — base (merge-base)
clickbench_partitioned — branch
File an issue against this benchmark runner |
Which issue does this PR close?
Rationale for this change
Queries that group on a high-cardinality dictionary column slow down as the group count grows. On ClickBench
hits_partitionedwithenable_rle_to_dictionary = true, this PR takes the suite from 78.8 s to 42.8 s, with Q33 and Q34 about 3.5x faster and peak memory slightly lower (bot run).A hash aggregate builds its output once and emits it in
batch_sizeslices, and slicing aDictionaryArraykeeps every value. Every slice therefore carries every group's value, so hashing, functions such aslower, the repartition coalescer and the final aggregate do work per slice that grows with the group count.What changes are included in this PR?
Start reading at
next_batchinaggregates/aggregate_hash_table/common.rs.hash_dictionaryand the newmap_dictionary_valueshandle at mostMAX_DICTIONARY_VALUES_PER_ROW(64) values per row and compact past that. Too high only gives up an improvement, while too low makes cheap work slower:lowerandupperbreak even at about 40 values per row with keys in random order. The filter and scan queries I measured showed no visible difference, so this is a guard, not a measured speedup.compact_dictionarydoes both, taking each referenced value once.take(values, keys)copies per row, and multi-column group keys repeat dictionary keys, so it can overflowi32offsets, need more rows than the key type holds, and copy a long value thousands of times.map_dictionary_valuesreplacesdictionary.with_values(f(values))in the string functions, Sparkhex,array_agg,regexp_replaceandsubstr, andclippy.tomldisallows callingwith_valuesdirectly. Ifffails, it retries on only the referenced values, becausefcan see values no row references. This replacessubstr's own fallback.Each aggregate slice now has its own values
Arc, so the hash cache proposed in #26816 would miss on these slices. The two haven't been measured together.Benchmarks
Q35, Q32 and Q40 read 1.05x to 1.10x slower by minimum. None groups on a dictionary column, so this PR only adds a type check per output batch for them, and the bot's noise is about 5-7% per query.
Local runs on a busy 12-core laptop: `lower`, memory limits, filter queries
Results were identical on every query. Treat small differences as noise.
lowerover a dictionary GROUP BY, which ClickBench doesn't cover:Q33 under
--memory-limit, one run each:Filter and scan queries using
reverse,initcap,LIKEand hashing after a filter, with the bound at 40: 0.94x to 1.05x of main.What is the testing strategy for this PR?
dictionary.sltcompares each function on aggregate output and on filter output with the same function on the decoded value. The comparison is projected, because a filter on the group key is pushed below the aggregate.next_batch,map_dictionary_valuesand hashing: repeated keys, NULL groups, more rows than the key type holds, and a value too long to copy per row.dictionary_slicedcase indatafusion/common/benches/with_hashes.rscovers 8 and 128 values per row.Are there any user-facing changes?
No behavior changes.
datafusion_common::utilsgainscompact_dictionary,map_dictionary_valuesandMAX_DICTIONARY_VALUES_PER_ROW.