Skip to content

perf: optimizing take_n for DictionaryGroupValuesColumn - #24476

Open
saadtajwar wants to merge 3 commits into
apache:mainfrom
saadtajwar:saadtajwar/DictionaryGroupValuesColumn-take-n-optimization
Open

perf: optimizing take_n for DictionaryGroupValuesColumn#24476
saadtajwar wants to merge 3 commits into
apache:mainfrom
saadtajwar:saadtajwar/DictionaryGroupValuesColumn-take-n-optimization

Conversation

@saadtajwar

@saadtajwar saadtajwar commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Rationale for this change (copied from issue)

DictionaryGroupValuesColumn::take_n emits the first n groups and rebuilds the remainder in-place. Every call hashes all surviving distinct values from scratch to reconstruct value_dedup, making repeated partial emissions O(G² / batch_size) over the lifetime of a query, where G is the total number of distinct values seen. Additionally, arrow::compute::take does not compact the backing storage for Utf8View, BinaryView, or nested dictionary value arrays; the rebuilt column retains a reference to the full original allocation rather than releasing memory proportional to the dropped groups. Under a high-cardinality dictionary key with frequent spill-driven partial emissions (e.g. streaming aggregation with a large fan-out), this combination causes both CPU and peak RSS to grow super-linearly with group count.

What changes are included in this PR?

  • Compact backing storage for rebuilt column
  • Updating value_dedup to only remove/move relevant slots instead of complete rebuild
  • Only adding leftover values to inner for the remainder loop

Are these changes tested?

Yes

Are there any user-facing changes?

No

@github-actions github-actions Bot added the physical-plan Changes to the physical-plan crate label Aug 19, 2026
@codecov-commenter

codecov-commenter commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 59.75610% with 33 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.30%. Comparing base (dbdc627) to head (901e301).

Files with missing lines Patch % Lines
...gregates/group_values/multi_group_by/dictionary.rs 59.75% 31 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #24476      +/-   ##
==========================================
- Coverage   81.31%   81.30%   -0.01%     
==========================================
  Files        1117     1117              
  Lines      395911   395981      +70     
  Branches   395911   395981      +70     
==========================================
+ Hits       321918   321948      +30     
- Misses      55177    55209      +32     
- Partials    18816    18824       +8     

☔ 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.

@saadtajwar
saadtajwar marked this pull request as ready for review August 20, 2026 15:40
@saadtajwar

Copy link
Copy Markdown
Contributor Author

@Rich-T-kid & @kumarUjjawal - ready for review! Thanks in advance for your feedback!

@saadtajwar saadtajwar changed the title perf: optimizing take_n for DictionaryGroupValuesColumn (DRAFT) perf: optimizing take_n for DictionaryGroupValuesColumn Aug 20, 2026
@Rich-T-kid

Copy link
Copy Markdown
Contributor

run benchmark dictionary_group_values

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c5358284067-1812-nfjkb 6.12.85+ #1 SMP Sat Jun 27 09:31:30 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 saadtajwar/DictionaryGroupValuesColumn-take-n-optimization (901e301) to dbdc627 (merge-base) diff

Run configuration
run benchmark dictionary_group_values

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 saadtajwar/DictionaryGroupValuesColumn-take-n-optimization (901e301) to dbdc627 (merge-base) diff

Run configuration
run benchmark dictionary_group_values
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                                    saadtajwar_DictionaryGroupValuesColumn-take-n-optimization
-----                                                                             ----                                    ----------------------------------------------------------
dict_intern_emit/intern_emit/size_65536_card_1000_null_0.00                       1.00    501.1±5.79µs 124.7 MElem/sec    1.01    506.2±5.87µs 123.5 MElem/sec
dict_intern_emit/intern_emit/size_65536_card_20_null_0.00                         1.00    422.5±6.12µs 147.9 MElem/sec    1.01    425.2±6.79µs 147.0 MElem/sec
dict_intern_emit/intern_emit/size_65536_card_300_null_0.00                        1.00    454.4±5.66µs 137.6 MElem/sec    1.01    460.0±6.43µs 135.9 MElem/sec
dict_intern_emit/intern_emit/size_65536_card_65536_null_0.00                      1.00      6.2±0.01ms 10.1 MElem/sec     1.02      6.3±0.01ms  9.9 MElem/sec
dict_intern_emit/intern_emit/size_65536_card_75_null_0.00                         1.00    443.7±5.83µs 140.9 MElem/sec    1.01    446.8±6.24µs 139.9 MElem/sec
dict_intern_emit/intern_emit/size_8192_card_1000_null_0.00                        1.00    126.0±9.63µs 62.0 MElem/sec     1.01    127.4±1.79µs 61.3 MElem/sec
dict_intern_emit/intern_emit/size_8192_card_20_null_0.00                          1.01     58.0±1.08µs 134.8 MElem/sec    1.00     57.3±0.82µs 136.3 MElem/sec
dict_intern_emit/intern_emit/size_8192_card_300_null_0.00                         1.00     77.5±5.36µs 100.8 MElem/sec    1.01     78.0±0.72µs 100.2 MElem/sec
dict_intern_emit/intern_emit/size_8192_card_75_null_0.00                          1.00     63.1±0.63µs 123.7 MElem/sec    1.01     63.5±0.68µs 123.0 MElem/sec
dict_intern_emit/intern_emit/size_8192_card_8192_null_0.00                        1.00    523.7±2.29µs 14.9 MElem/sec     1.02    532.7±1.53µs 14.7 MElem/sec
dict_repeated_intern_emit/repeated_intern_emit/size_65536_card_1000_null_0.10     1.00      2.4±0.00ms 105.2 MElem/sec    1.00      2.4±0.01ms 104.8 MElem/sec
dict_repeated_intern_emit/repeated_intern_emit/size_65536_card_20_null_0.10       1.01      2.2±0.01ms 112.4 MElem/sec    1.00      2.2±0.01ms 112.9 MElem/sec
dict_repeated_intern_emit/repeated_intern_emit/size_65536_card_300_null_0.10      1.00      2.3±0.00ms 108.5 MElem/sec    1.00      2.3±0.01ms 108.0 MElem/sec
dict_repeated_intern_emit/repeated_intern_emit/size_65536_card_65536_null_0.10    1.01     17.2±0.18ms 14.6 MElem/sec     1.00     17.0±0.17ms 14.7 MElem/sec
dict_repeated_intern_emit/repeated_intern_emit/size_65536_card_75_null_0.10       1.00      2.3±0.00ms 108.9 MElem/sec    1.00      2.3±0.01ms 108.8 MElem/sec
dict_repeated_intern_emit/repeated_intern_emit/size_8192_card_1000_null_0.10      1.01    403.1±1.21µs 77.5 MElem/sec     1.00    398.3±2.11µs 78.5 MElem/sec
dict_repeated_intern_emit/repeated_intern_emit/size_8192_card_20_null_0.10        1.04    271.1±2.34µs 115.3 MElem/sec    1.00    261.6±1.98µs 119.5 MElem/sec
dict_repeated_intern_emit/repeated_intern_emit/size_8192_card_300_null_0.10       1.02    313.0±1.57µs 99.8 MElem/sec     1.00    307.6±1.89µs 101.6 MElem/sec
dict_repeated_intern_emit/repeated_intern_emit/size_8192_card_75_null_0.10        1.03    285.4±1.61µs 109.5 MElem/sec    1.00    277.5±1.74µs 112.6 MElem/sec
dict_repeated_intern_emit/repeated_intern_emit/size_8192_card_8192_null_0.10      1.00   1229.0±4.37µs 25.4 MElem/sec     1.01   1238.8±4.94µs 25.2 MElem/sec
dict_scalar_append_equal/scalar_append_equal/size_65536_card_1000_null_0.10       1.00   1435.1±3.65µs 43.6 MElem/sec     1.00   1437.4±3.99µs 43.5 MElem/sec
dict_scalar_append_equal/scalar_append_equal/size_65536_card_20_null_0.10         1.00   1224.0±3.29µs 51.1 MElem/sec     1.00   1223.2±4.39µs 51.1 MElem/sec
dict_scalar_append_equal/scalar_append_equal/size_65536_card_300_null_0.10        1.00   1283.5±3.17µs 48.7 MElem/sec     1.02   1313.7±3.60µs 47.6 MElem/sec
dict_scalar_append_equal/scalar_append_equal/size_65536_card_65536_null_0.10      1.00     13.9±0.24ms  4.5 MElem/sec     1.00     13.9±0.10ms  4.5 MElem/sec
dict_scalar_append_equal/scalar_append_equal/size_65536_card_75_null_0.10         1.00   1248.4±3.24µs 50.1 MElem/sec     1.00   1246.0±2.96µs 50.2 MElem/sec
dict_take_n/take_n/size_65536_card_1000_null_0.10                                 1.03      2.6±0.00ms 97.7 MElem/sec     1.00      2.5±0.00ms 100.2 MElem/sec
dict_take_n/take_n/size_65536_card_20_null_0.10                                   1.03      2.2±0.01ms 113.5 MElem/sec    1.00      2.1±0.00ms 117.0 MElem/sec
dict_take_n/take_n/size_65536_card_300_null_0.10                                  1.02      2.3±0.00ms 107.1 MElem/sec    1.00      2.3±0.00ms 109.0 MElem/sec
dict_take_n/take_n/size_65536_card_65536_null_0.10                                1.00     25.5±0.07ms  9.8 MElem/sec     1.04     26.4±0.13ms  9.5 MElem/sec
dict_take_n/take_n/size_65536_card_75_null_0.10                                   1.02      2.3±0.01ms 109.8 MElem/sec    1.00      2.2±0.00ms 112.4 MElem/sec

Resource Usage

dictionary_group_values — base (merge-base)

Metric Value
Wall time 405.1s
Peak memory 497.4 MiB
Avg memory 87.3 MiB
CPU user 348.5s
CPU sys 21.7s
Peak spill 0 B

dictionary_group_values — branch

Metric Value
Wall time 465.1s
Peak memory 811.0 MiB
Avg memory 145.7 MiB
CPU user 362.0s
CPU sys 17.2s
Peak spill 0 B

File an issue against this benchmark runner

@Rich-T-kid

Copy link
Copy Markdown
Contributor
dict_take_n/take_n/size_65536_card_1000_null_0.10                                 1.03      2.6±0.00ms 97.7 MElem/sec     1.00      2.5±0.00ms 100.2 MElem/sec
dict_take_n/take_n/size_65536_card_20_null_0.10                                   1.03      2.2±0.01ms 113.5 MElem/sec    1.00      2.1±0.00ms 117.0 MElem/sec
dict_take_n/take_n/size_65536_card_300_null_0.10                                  1.02      2.3±0.00ms 107.1 MElem/sec    1.00      2.3±0.00ms 109.0 MElem/sec
dict_take_n/take_n/size_65536_card_65536_null_0.10                                1.00     25.5±0.07ms  9.8 MElem/sec     1.04     26.4±0.13ms  9.5 MElem/sec
dict_take_n/take_n/size_65536_card_75_null_0.10                                   1.02      2.3±0.01ms 109.8 MElem/sec    1.00      2.2±0.00ms 112.4 MElem/sec

@saadtajwar

@saadtajwar

Copy link
Copy Markdown
Contributor Author
dict_take_n/take_n/size_65536_card_1000_null_0.10                                 1.03      2.6±0.00ms 97.7 MElem/sec     1.00      2.5±0.00ms 100.2 MElem/sec
dict_take_n/take_n/size_65536_card_20_null_0.10                                   1.03      2.2±0.01ms 113.5 MElem/sec    1.00      2.1±0.00ms 117.0 MElem/sec
dict_take_n/take_n/size_65536_card_300_null_0.10                                  1.02      2.3±0.00ms 107.1 MElem/sec    1.00      2.3±0.00ms 109.0 MElem/sec
dict_take_n/take_n/size_65536_card_65536_null_0.10                                1.00     25.5±0.07ms  9.8 MElem/sec     1.04     26.4±0.13ms  9.5 MElem/sec
dict_take_n/take_n/size_65536_card_75_null_0.10                                   1.02      2.3±0.01ms 109.8 MElem/sec    1.00      2.2±0.00ms 112.4 MElem/sec

@saadtajwar

Oof, if I'm reading this correctly this doesn't look like a huge win for us here...any thoughts on how we want move forward here? I feel like we could either:

  • decide the current benchmarks capture the common use cases and don't warrant these changes
  • decide the current benchmarks capture the common use cases and still warrant changes but maybe not the specific changes we have - we could look at the actual change set and either modify or more likely remove some of these optimizations
  • decide to modify the benchmarks to try and better exercise the paths that our current changes would optimize for (maybe increase the number of emit cycles to be >>> 4/5, use more distinct leftovers, use more expensive values with longer strings/Utf8View)

My preference would be the last option, but I'm obviously biased in wanting to see these changes move the needle 😆 open to any feedback!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

physical-plan Changes to the physical-plan crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Optimize take_n on DictionaryGroupValuesColumn<K>

4 participants