Skip to content

Rich t kid/dictionary encoding hash optmize#21589

Open
Rich-T-kid wants to merge 20 commits intoapache:mainfrom
Rich-T-kid:rich-t-kid/Dictionary-encoding-Hash-optmize
Open

Rich t kid/dictionary encoding hash optmize#21589
Rich-T-kid wants to merge 20 commits intoapache:mainfrom
Rich-T-kid:rich-t-kid/Dictionary-encoding-Hash-optmize

Conversation

@Rich-T-kid
Copy link
Copy Markdown
Contributor

@Rich-T-kid Rich-T-kid commented Apr 13, 2026

Which issue does this PR close?

This PR make an effort towards #7000 & closing materializing dictionary columns + #21466

Rationale for this change

This PR implements a specialized GroupValues implementation for single-column dictionary-encoded GROUP BY columns, motivated by the dictionary encoding efficiency work tracked in #7647. GroupValuesRows was inefficient for dictionary-encoded columns because it runs the full RowConverter pipeline on every row, decoding the dictionary back to its underlying string values and serializing them into a packed row format for comparison, completely discarding the integer key structure that dictionary encoding provides and doing O(n) expensive string operations when the same d distinct values could be processed just once.
Initial approach
the first implementation used HashMap<ScalarValue, usize> to map group values to group indices. While correct, profiling revealed this was significantly slower than GroupValuesRows due to per-row heap allocation from ScalarValue::try_from_array, with ~60% of intern time spent on allocation and deallocation.
Final approach
after profiling and iteration the implementation now uses a two-pass strategy that directly exploits dictionary encoding's structure. Pass 1 iterates the small values array (d distinct values) once, building a key_to_group lookup via raw byte comparison and pre-computed hashes. Pass 2 iterates the keys array (n rows) using only cheap array index lookups — no hashing, no byte comparison, no hashmap lookup in the hot path. This reduces the expensive work from O(n) to O(d) per batch.
Benchmarks show consistent 1.9x–2.7x improvement over GroupValuesRows across all cardinality and batch size configurations with no regressions.

What changes are included in this PR?

Update the match statement in new_group_values to include a custom dictionary encoding branch that works for single fields that are of type Dictionary array

Are these changes tested?

Yes a large portion of the PR are test, these include

  1. benchmark's to show a significant improvement over the generic GroupValueRows implementation
  2. functional test that prove the logically the group values is upheld

Are there any user-facing changes?

No everything is internal.

@github-actions github-actions bot added the physical-plan Changes to the physical-plan crate label Apr 13, 2026
@Rich-T-kid
Copy link
Copy Markdown
Contributor Author

Currenly the CI is breaking because my current emit implementations returns the values dirrectly instead of returning a dictionary encoded column. & im not handling nulls.

@Rich-T-kid Rich-T-kid force-pushed the rich-t-kid/Dictionary-encoding-Hash-optmize branch from 8ea71fd to cc18a8f Compare April 13, 2026 16:07
@Rich-T-kid
Copy link
Copy Markdown
Contributor Author

run benchmarks

@adriangbot
Copy link
Copy Markdown

@gabotechs
Copy link
Copy Markdown
Contributor

run benchmarks

@adriangbot
Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c4242152155-1208-bgwdd 6.12.55+ #1 SMP Sun Feb 1 08:59:41 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 rich-t-kid/Dictionary-encoding-Hash-optmize (aa69892) to 37cd3de (merge-base) diff using: clickbench_partitioned
Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot
Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c4242152155-1210-wjf8p 6.12.55+ #1 SMP Sun Feb 1 08:59:41 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 rich-t-kid/Dictionary-encoding-Hash-optmize (aa69892) to 37cd3de (merge-base) diff using: tpch
Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot
Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c4242152155-1209-85flv 6.12.55+ #1 SMP Sun Feb 1 08:59:41 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 rich-t-kid/Dictionary-encoding-Hash-optmize (aa69892) to 37cd3de (merge-base) diff using: tpcds
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)

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

Comparing HEAD and rich-t-kid_Dictionary-encoding-Hash-optmize
--------------------
Benchmark tpch_sf1.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Query     ┃                           HEAD ┃ rich-t-kid_Dictionary-encoding-Hash-optmize ┃    Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ QQuery 1  │ 45.17 / 46.04 ±0.64 / 47.09 ms │              45.38 / 46.02 ±0.75 / 47.46 ms │ no change │
│ QQuery 2  │ 21.03 / 21.25 ±0.27 / 21.74 ms │              21.07 / 21.53 ±0.50 / 22.48 ms │ no change │
│ QQuery 3  │ 31.41 / 31.56 ±0.13 / 31.76 ms │              31.42 / 31.94 ±0.58 / 33.07 ms │ no change │
│ QQuery 4  │ 19.91 / 21.17 ±0.91 / 22.24 ms │              20.26 / 21.16 ±0.68 / 22.29 ms │ no change │
│ QQuery 5  │ 47.80 / 49.52 ±1.35 / 51.30 ms │              47.54 / 48.55 ±1.68 / 51.89 ms │ no change │
│ QQuery 6  │ 16.94 / 17.08 ±0.15 / 17.36 ms │              17.19 / 17.51 ±0.45 / 18.39 ms │ no change │
│ QQuery 7  │ 53.99 / 54.85 ±0.55 / 55.66 ms │              53.89 / 55.01 ±0.67 / 55.91 ms │ no change │
│ QQuery 8  │ 47.06 / 49.08 ±2.01 / 52.71 ms │              47.95 / 48.21 ±0.30 / 48.78 ms │ no change │
│ QQuery 9  │ 54.51 / 55.99 ±2.06 / 60.00 ms │              53.86 / 54.75 ±0.83 / 55.98 ms │ no change │
│ QQuery 10 │ 70.66 / 71.15 ±0.31 / 71.53 ms │              70.10 / 70.70 ±0.51 / 71.28 ms │ no change │
│ QQuery 11 │ 13.78 / 14.34 ±0.56 / 15.22 ms │              13.77 / 14.14 ±0.37 / 14.80 ms │ no change │
│ QQuery 12 │ 27.96 / 28.18 ±0.15 / 28.42 ms │              27.66 / 28.07 ±0.22 / 28.27 ms │ no change │
│ QQuery 13 │ 37.48 / 38.70 ±0.92 / 39.70 ms │              37.88 / 38.99 ±0.70 / 39.97 ms │ no change │
│ QQuery 14 │ 28.24 / 28.44 ±0.15 / 28.63 ms │              28.78 / 29.13 ±0.41 / 29.77 ms │ no change │
│ QQuery 15 │ 33.05 / 33.53 ±0.50 / 34.48 ms │              33.72 / 33.88 ±0.19 / 34.22 ms │ no change │
│ QQuery 16 │ 15.91 / 16.05 ±0.12 / 16.20 ms │              15.99 / 16.51 ±0.36 / 16.91 ms │ no change │
│ QQuery 17 │ 72.01 / 73.43 ±1.30 / 75.16 ms │              71.46 / 72.13 ±0.90 / 73.87 ms │ no change │
│ QQuery 18 │ 76.35 / 77.24 ±0.55 / 78.00 ms │              75.79 / 76.78 ±0.77 / 77.81 ms │ no change │
│ QQuery 19 │ 37.37 / 38.02 ±0.76 / 39.20 ms │              37.77 / 38.21 ±0.42 / 38.96 ms │ no change │
│ QQuery 20 │ 39.53 / 40.86 ±0.89 / 42.31 ms │              39.61 / 40.34 ±0.68 / 41.26 ms │ no change │
│ QQuery 21 │ 62.95 / 64.17 ±1.18 / 65.74 ms │              63.24 / 64.95 ±1.25 / 66.57 ms │ no change │
│ QQuery 22 │ 17.13 / 17.57 ±0.32 / 18.01 ms │              17.58 / 18.07 ±0.32 / 18.56 ms │ no change │
└───────────┴────────────────────────────────┴─────────────────────────────────────────────┴───────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┓
┃ Benchmark Summary                                          ┃          ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━┩
│ Total Time (HEAD)                                          │ 888.26ms │
│ Total Time (rich-t-kid_Dictionary-encoding-Hash-optmize)   │ 886.59ms │
│ Average Time (HEAD)                                        │  40.38ms │
│ Average Time (rich-t-kid_Dictionary-encoding-Hash-optmize) │  40.30ms │
│ Queries Faster                                             │        0 │
│ Queries Slower                                             │        0 │
│ Queries with No Change                                     │       22 │
│ Queries with Failure                                       │        0 │
└────────────────────────────────────────────────────────────┴──────────┘

Resource Usage

tpch — base (merge-base)

Metric Value
Wall time 4.7s
Peak memory 4.0 GiB
Avg memory 3.6 GiB
CPU user 33.0s
CPU sys 2.8s
Peak spill 0 B

tpch — branch

Metric Value
Wall time 4.7s
Peak memory 4.0 GiB
Avg memory 3.6 GiB
CPU user 33.2s
CPU sys 2.7s
Peak spill 0 B

File an issue against this benchmark runner

@adriangbot
Copy link
Copy Markdown

🤖 Benchmark completed (GKE) | trigger

Instance: c4a-highmem-16 (12 vCPU / 65 GiB)

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

Comparing HEAD and rich-t-kid_Dictionary-encoding-Hash-optmize
--------------------
Benchmark tpcds_sf1.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ Query     ┃                                     HEAD ┃ rich-t-kid_Dictionary-encoding-Hash-optmize ┃       Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ QQuery 1  │           43.47 / 44.23 ±0.78 / 45.71 ms │              44.05 / 45.02 ±0.79 / 45.97 ms │    no change │
│ QQuery 2  │        145.23 / 146.39 ±0.93 / 147.65 ms │           145.70 / 146.81 ±0.83 / 147.82 ms │    no change │
│ QQuery 3  │        113.80 / 115.90 ±1.48 / 118.21 ms │           114.46 / 115.90 ±0.85 / 116.81 ms │    no change │
│ QQuery 4  │    1371.78 / 1402.01 ±29.61 / 1450.43 ms │        1416.99 / 1425.53 ±7.71 / 1439.03 ms │    no change │
│ QQuery 5  │        173.69 / 175.79 ±1.77 / 178.72 ms │           173.11 / 174.30 ±1.57 / 177.39 ms │    no change │
│ QQuery 6  │    1006.29 / 1040.83 ±28.46 / 1092.41 ms │       1019.07 / 1044.28 ±23.82 / 1082.73 ms │    no change │
│ QQuery 7  │        361.17 / 362.79 ±1.31 / 365.12 ms │           355.90 / 359.06 ±2.09 / 361.38 ms │    no change │
│ QQuery 8  │        116.68 / 118.27 ±1.48 / 121.05 ms │           116.67 / 117.92 ±0.83 / 118.87 ms │    no change │
│ QQuery 9  │        101.25 / 104.66 ±2.67 / 108.45 ms │           102.22 / 104.93 ±2.22 / 107.90 ms │    no change │
│ QQuery 10 │        110.43 / 110.74 ±0.21 / 110.97 ms │           109.63 / 110.67 ±0.67 / 111.58 ms │    no change │
│ QQuery 11 │     993.31 / 1020.17 ±19.24 / 1045.98 ms │         982.41 / 999.64 ±10.42 / 1011.76 ms │    no change │
│ QQuery 12 │           44.83 / 45.87 ±0.89 / 47.51 ms │              46.47 / 48.40 ±1.59 / 51.19 ms │ 1.06x slower │
│ QQuery 13 │        406.44 / 407.78 ±1.13 / 409.68 ms │           404.25 / 409.15 ±2.76 / 412.58 ms │    no change │
│ QQuery 14 │     1013.01 / 1024.44 ±8.10 / 1037.43 ms │        1034.70 / 1039.69 ±4.14 / 1045.88 ms │    no change │
│ QQuery 15 │           16.20 / 17.43 ±1.21 / 19.44 ms │              16.34 / 17.12 ±0.85 / 18.67 ms │    no change │
│ QQuery 16 │           41.12 / 42.81 ±2.11 / 46.95 ms │              41.12 / 42.64 ±1.19 / 44.44 ms │    no change │
│ QQuery 17 │        241.53 / 243.67 ±1.95 / 246.84 ms │           242.68 / 244.68 ±1.36 / 246.29 ms │    no change │
│ QQuery 18 │        130.91 / 132.30 ±1.36 / 134.47 ms │           130.95 / 133.23 ±1.43 / 135.45 ms │    no change │
│ QQuery 19 │        155.55 / 156.91 ±1.38 / 159.29 ms │           157.37 / 157.86 ±0.33 / 158.21 ms │    no change │
│ QQuery 20 │           14.07 / 14.34 ±0.14 / 14.47 ms │              14.15 / 14.84 ±0.52 / 15.45 ms │    no change │
│ QQuery 21 │           19.94 / 20.25 ±0.23 / 20.52 ms │              20.18 / 20.47 ±0.19 / 20.73 ms │    no change │
│ QQuery 22 │        494.92 / 498.32 ±4.38 / 506.94 ms │           497.85 / 504.85 ±5.61 / 511.07 ms │    no change │
│ QQuery 23 │        887.05 / 893.50 ±5.67 / 900.91 ms │           907.37 / 921.52 ±8.62 / 932.59 ms │    no change │
│ QQuery 24 │        418.76 / 421.36 ±1.86 / 424.17 ms │           420.90 / 424.29 ±2.19 / 426.53 ms │    no change │
│ QQuery 25 │        353.21 / 356.50 ±1.99 / 358.63 ms │           354.65 / 359.36 ±2.76 / 363.02 ms │    no change │
│ QQuery 26 │           83.77 / 86.03 ±2.73 / 91.04 ms │              82.95 / 85.01 ±1.33 / 86.57 ms │    no change │
│ QQuery 27 │        348.05 / 352.70 ±4.68 / 361.04 ms │           352.46 / 356.08 ±3.22 / 360.59 ms │    no change │
│ QQuery 28 │        148.71 / 150.18 ±0.98 / 151.24 ms │           149.81 / 151.83 ±2.37 / 155.29 ms │    no change │
│ QQuery 29 │        300.83 / 302.91 ±1.52 / 305.08 ms │           301.70 / 303.80 ±1.79 / 306.01 ms │    no change │
│ QQuery 30 │           46.14 / 46.80 ±0.68 / 48.11 ms │              44.37 / 45.95 ±1.15 / 47.78 ms │    no change │
│ QQuery 31 │        172.89 / 173.99 ±1.42 / 176.79 ms │           170.19 / 173.64 ±2.00 / 176.27 ms │    no change │
│ QQuery 32 │           58.50 / 59.31 ±0.55 / 60.11 ms │              58.14 / 59.47 ±0.90 / 60.89 ms │    no change │
│ QQuery 33 │        140.33 / 143.58 ±1.85 / 145.41 ms │           140.52 / 143.16 ±1.88 / 145.68 ms │    no change │
│ QQuery 34 │        108.03 / 108.51 ±0.37 / 109.04 ms │           107.76 / 108.40 ±0.52 / 109.02 ms │    no change │
│ QQuery 35 │        108.41 / 110.19 ±1.62 / 112.64 ms │           110.80 / 112.17 ±0.83 / 113.06 ms │    no change │
│ QQuery 36 │        215.85 / 220.01 ±3.72 / 225.43 ms │           219.67 / 222.15 ±1.86 / 225.37 ms │    no change │
│ QQuery 37 │        177.29 / 181.35 ±2.25 / 183.62 ms │           179.98 / 182.28 ±1.67 / 184.96 ms │    no change │
│ QQuery 38 │           85.58 / 88.84 ±1.67 / 90.30 ms │              84.65 / 90.20 ±3.86 / 96.35 ms │    no change │
│ QQuery 39 │        127.08 / 129.84 ±1.99 / 132.75 ms │           129.32 / 130.89 ±1.39 / 132.50 ms │    no change │
│ QQuery 40 │        112.99 / 117.04 ±3.74 / 123.80 ms │           112.71 / 116.07 ±5.79 / 127.65 ms │    no change │
│ QQuery 41 │           14.25 / 15.05 ±0.45 / 15.61 ms │              14.67 / 15.87 ±0.87 / 16.92 ms │ 1.05x slower │
│ QQuery 42 │        109.00 / 109.61 ±0.59 / 110.73 ms │           107.58 / 109.29 ±1.96 / 113.09 ms │    no change │
│ QQuery 43 │           83.19 / 84.28 ±0.97 / 86.03 ms │              83.98 / 84.52 ±0.63 / 85.70 ms │    no change │
│ QQuery 44 │           11.81 / 12.35 ±0.70 / 13.70 ms │              11.82 / 12.19 ±0.30 / 12.68 ms │    no change │
│ QQuery 45 │           53.06 / 53.53 ±0.34 / 53.94 ms │              53.19 / 54.92 ±1.69 / 58.08 ms │    no change │
│ QQuery 46 │        232.74 / 234.67 ±1.05 / 235.65 ms │           239.16 / 241.42 ±1.71 / 243.98 ms │    no change │
│ QQuery 47 │       724.49 / 733.52 ±11.74 / 756.54 ms │           749.54 / 755.43 ±4.79 / 763.17 ms │    no change │
│ QQuery 48 │        288.06 / 293.59 ±3.06 / 297.27 ms │           289.52 / 297.77 ±5.03 / 304.25 ms │    no change │
│ QQuery 49 │        252.41 / 256.12 ±2.67 / 259.96 ms │           256.08 / 259.72 ±3.29 / 265.41 ms │    no change │
│ QQuery 50 │        226.85 / 233.20 ±3.73 / 237.83 ms │           233.94 / 236.72 ±1.88 / 239.12 ms │    no change │
│ QQuery 51 │        182.49 / 185.22 ±1.51 / 186.85 ms │           182.45 / 185.97 ±2.62 / 189.85 ms │    no change │
│ QQuery 52 │        108.81 / 109.79 ±0.66 / 110.46 ms │           108.47 / 109.81 ±1.04 / 111.62 ms │    no change │
│ QQuery 53 │        103.65 / 104.55 ±0.95 / 106.37 ms │           103.69 / 104.55 ±0.78 / 106.03 ms │    no change │
│ QQuery 54 │        149.54 / 150.94 ±1.14 / 152.67 ms │           147.10 / 150.32 ±1.89 / 152.60 ms │    no change │
│ QQuery 55 │        107.95 / 109.22 ±0.78 / 110.09 ms │           107.01 / 108.82 ±1.73 / 111.87 ms │    no change │
│ QQuery 56 │        141.36 / 142.92 ±0.80 / 143.46 ms │           143.05 / 144.36 ±1.01 / 145.46 ms │    no change │
│ QQuery 57 │        173.39 / 176.29 ±1.70 / 178.24 ms │           176.02 / 179.01 ±2.62 / 183.76 ms │    no change │
│ QQuery 58 │        292.56 / 300.73 ±6.54 / 309.36 ms │           294.78 / 304.57 ±8.71 / 319.87 ms │    no change │
│ QQuery 59 │        198.82 / 199.53 ±0.58 / 200.46 ms │           198.49 / 200.92 ±1.59 / 202.69 ms │    no change │
│ QQuery 60 │        143.53 / 144.67 ±1.16 / 146.75 ms │           146.11 / 147.15 ±0.64 / 147.82 ms │    no change │
│ QQuery 61 │        171.65 / 173.85 ±1.40 / 175.25 ms │           176.33 / 177.65 ±1.08 / 179.58 ms │    no change │
│ QQuery 62 │       890.15 / 920.38 ±28.00 / 967.86 ms │         926.36 / 953.59 ±40.77 / 1033.12 ms │    no change │
│ QQuery 63 │        104.87 / 106.93 ±1.44 / 108.79 ms │           103.17 / 106.27 ±2.32 / 110.19 ms │    no change │
│ QQuery 64 │        698.54 / 708.14 ±6.84 / 719.62 ms │           708.55 / 718.45 ±6.60 / 727.81 ms │    no change │
│ QQuery 65 │        252.43 / 256.06 ±2.13 / 258.48 ms │           262.56 / 264.75 ±2.18 / 268.81 ms │    no change │
│ QQuery 66 │        245.63 / 248.09 ±2.73 / 253.10 ms │          241.14 / 258.57 ±11.39 / 276.86 ms │    no change │
│ QQuery 67 │        321.93 / 328.80 ±6.39 / 338.77 ms │           315.58 / 324.61 ±8.46 / 336.54 ms │    no change │
│ QQuery 68 │        282.59 / 289.51 ±5.77 / 296.49 ms │           281.36 / 288.89 ±4.53 / 293.21 ms │    no change │
│ QQuery 69 │        106.21 / 107.01 ±0.44 / 107.50 ms │           103.55 / 105.55 ±1.20 / 106.85 ms │    no change │
│ QQuery 70 │       338.48 / 354.29 ±10.59 / 369.77 ms │          329.98 / 345.74 ±12.85 / 367.05 ms │    no change │
│ QQuery 71 │        134.73 / 138.65 ±2.47 / 142.39 ms │           134.56 / 135.04 ±0.30 / 135.46 ms │    no change │
│ QQuery 72 │       718.65 / 731.75 ±11.12 / 751.37 ms │          718.08 / 732.86 ±10.58 / 749.90 ms │    no change │
│ QQuery 73 │        106.08 / 107.66 ±1.27 / 109.37 ms │           104.51 / 106.53 ±1.24 / 108.02 ms │    no change │
│ QQuery 74 │        625.24 / 630.79 ±4.97 / 639.20 ms │           604.93 / 609.53 ±3.69 / 616.03 ms │    no change │
│ QQuery 75 │        277.62 / 280.28 ±2.43 / 284.88 ms │           277.29 / 281.55 ±2.38 / 284.27 ms │    no change │
│ QQuery 76 │        133.88 / 135.60 ±1.03 / 136.77 ms │           133.48 / 135.74 ±2.20 / 139.59 ms │    no change │
│ QQuery 77 │        189.58 / 191.52 ±1.65 / 193.96 ms │           189.99 / 191.15 ±1.00 / 192.53 ms │    no change │
│ QQuery 78 │        352.63 / 358.29 ±3.90 / 364.78 ms │           354.58 / 359.88 ±3.68 / 364.23 ms │    no change │
│ QQuery 79 │        239.43 / 243.09 ±2.13 / 245.48 ms │           234.64 / 236.97 ±1.92 / 239.35 ms │    no change │
│ QQuery 80 │        331.10 / 335.01 ±2.74 / 338.29 ms │           332.37 / 335.44 ±2.21 / 337.31 ms │    no change │
│ QQuery 81 │           27.66 / 28.72 ±1.32 / 31.30 ms │              27.31 / 28.07 ±0.50 / 28.85 ms │    no change │
│ QQuery 82 │        202.49 / 204.72 ±1.26 / 206.29 ms │           203.56 / 205.64 ±1.15 / 206.85 ms │    no change │
│ QQuery 83 │           41.47 / 43.44 ±2.25 / 47.84 ms │              41.21 / 42.79 ±1.55 / 45.61 ms │    no change │
│ QQuery 84 │           50.28 / 50.94 ±0.58 / 51.73 ms │              49.94 / 50.89 ±0.94 / 52.51 ms │    no change │
│ QQuery 85 │        152.24 / 153.97 ±1.52 / 156.09 ms │           152.85 / 155.53 ±1.92 / 158.84 ms │    no change │
│ QQuery 86 │           39.92 / 40.39 ±0.48 / 41.12 ms │              39.69 / 40.70 ±1.19 / 42.92 ms │    no change │
│ QQuery 87 │           87.68 / 91.21 ±3.29 / 97.08 ms │             88.43 / 93.25 ±4.13 / 100.68 ms │    no change │
│ QQuery 88 │        101.39 / 102.63 ±1.16 / 104.76 ms │           102.00 / 102.73 ±0.50 / 103.36 ms │    no change │
│ QQuery 89 │        119.50 / 120.86 ±0.79 / 121.89 ms │           120.60 / 122.57 ±1.45 / 124.01 ms │    no change │
│ QQuery 90 │           24.88 / 26.01 ±0.79 / 27.07 ms │              23.96 / 24.91 ±0.66 / 25.79 ms │    no change │
│ QQuery 91 │           63.96 / 66.44 ±1.60 / 68.33 ms │              64.96 / 66.80 ±0.97 / 67.68 ms │    no change │
│ QQuery 92 │           58.59 / 59.54 ±0.64 / 60.57 ms │              58.83 / 59.37 ±0.47 / 59.93 ms │    no change │
│ QQuery 93 │        193.15 / 195.66 ±1.42 / 197.35 ms │           196.93 / 199.22 ±2.14 / 202.10 ms │    no change │
│ QQuery 94 │           62.75 / 63.16 ±0.29 / 63.59 ms │              62.52 / 63.07 ±0.53 / 64.08 ms │    no change │
│ QQuery 95 │        135.00 / 135.61 ±0.81 / 137.20 ms │           136.29 / 138.18 ±1.24 / 140.14 ms │    no change │
│ QQuery 96 │           75.17 / 76.77 ±1.50 / 78.78 ms │              75.26 / 76.58 ±1.04 / 78.21 ms │    no change │
│ QQuery 97 │        132.42 / 134.28 ±1.24 / 136.18 ms │           135.35 / 137.01 ±1.21 / 138.94 ms │    no change │
│ QQuery 98 │        158.14 / 159.00 ±0.75 / 159.93 ms │           158.64 / 160.74 ±1.65 / 163.69 ms │    no change │
│ QQuery 99 │ 10830.51 / 10854.73 ±24.50 / 10884.81 ms │    10795.70 / 10841.72 ±49.04 / 10909.15 ms │    no change │
└───────────┴──────────────────────────────────────────┴─────────────────────────────────────────────┴──────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary                                          ┃            ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD)                                          │ 34092.09ms │
│ Total Time (rich-t-kid_Dictionary-encoding-Hash-optmize)   │ 34245.13ms │
│ Average Time (HEAD)                                        │   344.36ms │
│ Average Time (rich-t-kid_Dictionary-encoding-Hash-optmize) │   345.91ms │
│ Queries Faster                                             │          0 │
│ Queries Slower                                             │          2 │
│ Queries with No Change                                     │         97 │
│ Queries with Failure                                       │          0 │
└────────────────────────────────────────────────────────────┴────────────┘

Resource Usage

tpcds — base (merge-base)

Metric Value
Wall time 170.8s
Peak memory 5.8 GiB
Avg memory 4.8 GiB
CPU user 274.4s
CPU sys 17.9s
Peak spill 0 B

tpcds — branch

Metric Value
Wall time 171.5s
Peak memory 5.7 GiB
Avg memory 4.7 GiB
CPU user 275.3s
CPU sys 18.2s
Peak spill 0 B

File an issue against this benchmark runner

@adriangbot
Copy link
Copy Markdown

🤖 Benchmark completed (GKE) | trigger

Instance: c4a-highmem-16 (12 vCPU / 65 GiB)

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

Comparing HEAD and rich-t-kid_Dictionary-encoding-Hash-optmize
--------------------
Benchmark clickbench_partitioned.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query     ┃                                  HEAD ┃ rich-t-kid_Dictionary-encoding-Hash-optmize ┃        Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 0  │          1.31 / 4.52 ±6.32 / 17.16 ms │                1.33 / 4.60 ±6.40 / 17.40 ms │     no change │
│ QQuery 1  │        14.36 / 14.75 ±0.37 / 15.37 ms │              14.74 / 15.44 ±0.39 / 15.90 ms │     no change │
│ QQuery 2  │        45.15 / 45.90 ±0.44 / 46.51 ms │              45.67 / 45.97 ±0.27 / 46.44 ms │     no change │
│ QQuery 3  │        43.83 / 46.26 ±1.38 / 47.91 ms │              44.62 / 46.23 ±1.13 / 47.52 ms │     no change │
│ QQuery 4  │    309.10 / 321.74 ±11.73 / 342.44 ms │          289.85 / 307.70 ±12.53 / 324.09 ms │     no change │
│ QQuery 5  │     348.09 / 354.69 ±4.81 / 362.38 ms │          350.92 / 365.25 ±11.69 / 380.29 ms │     no change │
│ QQuery 6  │           6.39 / 7.14 ±0.44 / 7.75 ms │                 5.24 / 7.01 ±1.63 / 9.81 ms │     no change │
│ QQuery 7  │        16.50 / 17.27 ±0.77 / 18.57 ms │              17.15 / 18.35 ±1.32 / 20.90 ms │  1.06x slower │
│ QQuery 8  │    421.92 / 445.71 ±12.76 / 459.26 ms │           447.41 / 457.46 ±7.84 / 470.36 ms │     no change │
│ QQuery 9  │    644.71 / 661.04 ±18.06 / 689.86 ms │          658.54 / 690.29 ±22.67 / 712.97 ms │     no change │
│ QQuery 10 │      98.82 / 101.15 ±3.67 / 108.44 ms │             95.14 / 98.50 ±2.20 / 101.79 ms │     no change │
│ QQuery 11 │     109.97 / 112.01 ±1.47 / 114.04 ms │           110.39 / 112.60 ±2.45 / 116.95 ms │     no change │
│ QQuery 12 │     375.21 / 381.38 ±7.31 / 393.55 ms │          348.66 / 372.53 ±13.97 / 391.09 ms │     no change │
│ QQuery 13 │     508.99 / 518.26 ±9.71 / 536.14 ms │          459.85 / 477.14 ±12.38 / 498.47 ms │ +1.09x faster │
│ QQuery 14 │     386.55 / 394.85 ±5.98 / 400.51 ms │          361.57 / 373.85 ±14.02 / 399.01 ms │ +1.06x faster │
│ QQuery 15 │     407.09 / 415.24 ±4.26 / 418.45 ms │          359.97 / 387.46 ±23.70 / 421.51 ms │ +1.07x faster │
│ QQuery 16 │    739.93 / 765.87 ±19.89 / 797.45 ms │          766.95 / 778.13 ±15.59 / 808.81 ms │     no change │
│ QQuery 17 │    734.23 / 756.71 ±15.96 / 771.03 ms │           745.17 / 757.69 ±8.83 / 768.23 ms │     no change │
│ QQuery 18 │ 1409.11 / 1442.53 ±25.30 / 1472.57 ms │       1457.81 / 1568.34 ±69.50 / 1657.27 ms │  1.09x slower │
│ QQuery 19 │        37.42 / 39.63 ±1.29 / 41.18 ms │              34.80 / 37.60 ±1.94 / 40.21 ms │ +1.05x faster │
│ QQuery 20 │    711.38 / 725.22 ±13.50 / 749.08 ms │           722.82 / 730.96 ±8.20 / 744.98 ms │     no change │
│ QQuery 21 │     760.60 / 768.69 ±7.65 / 782.52 ms │           770.35 / 775.98 ±4.26 / 781.45 ms │     no change │
│ QQuery 22 │  1139.40 / 1153.50 ±8.00 / 1161.81 ms │        1135.49 / 1143.65 ±9.02 / 1158.54 ms │     no change │
│ QQuery 23 │ 3147.96 / 3175.13 ±19.91 / 3206.23 ms │       3194.41 / 3237.72 ±40.02 / 3294.45 ms │     no change │
│ QQuery 24 │      99.84 / 103.39 ±2.51 / 107.42 ms │           102.11 / 103.19 ±0.97 / 104.62 ms │     no change │
│ QQuery 25 │     141.57 / 143.86 ±1.69 / 146.33 ms │           143.76 / 145.04 ±1.55 / 148.08 ms │     no change │
│ QQuery 26 │     102.78 / 106.52 ±2.78 / 110.28 ms │            99.56 / 103.04 ±2.28 / 106.28 ms │     no change │
│ QQuery 27 │     849.15 / 853.18 ±4.48 / 861.70 ms │          856.51 / 866.61 ±10.35 / 886.52 ms │     no change │
│ QQuery 28 │ 7796.11 / 7839.68 ±39.09 / 7898.77 ms │       7737.26 / 7816.71 ±53.87 / 7906.76 ms │     no change │
│ QQuery 29 │        53.56 / 57.17 ±3.34 / 62.35 ms │              51.58 / 56.22 ±4.71 / 61.98 ms │     no change │
│ QQuery 30 │    363.50 / 384.31 ±17.46 / 415.08 ms │           387.33 / 392.27 ±4.21 / 398.45 ms │     no change │
│ QQuery 31 │    382.22 / 398.28 ±19.61 / 435.89 ms │          392.19 / 410.77 ±17.27 / 436.60 ms │     no change │
│ QQuery 32 │ 1085.51 / 1098.77 ±15.67 / 1126.13 ms │       1101.11 / 1165.55 ±34.42 / 1204.97 ms │  1.06x slower │
│ QQuery 33 │ 1478.07 / 1536.38 ±37.09 / 1592.79 ms │       1536.67 / 1552.46 ±15.92 / 1578.67 ms │     no change │
│ QQuery 34 │ 1493.74 / 1555.45 ±41.89 / 1598.50 ms │       1537.28 / 1559.82 ±17.86 / 1591.86 ms │     no change │
│ QQuery 35 │     449.59 / 463.77 ±8.12 / 472.68 ms │          389.75 / 417.82 ±28.61 / 469.79 ms │ +1.11x faster │
│ QQuery 36 │     128.19 / 134.52 ±5.50 / 143.01 ms │           126.13 / 132.12 ±4.20 / 137.87 ms │     no change │
│ QQuery 37 │        52.79 / 54.39 ±1.50 / 56.45 ms │              47.01 / 50.35 ±2.16 / 52.30 ms │ +1.08x faster │
│ QQuery 38 │        76.74 / 79.07 ±1.85 / 81.96 ms │              75.72 / 78.66 ±2.34 / 81.25 ms │     no change │
│ QQuery 39 │    209.69 / 233.23 ±12.57 / 244.59 ms │          210.60 / 225.24 ±13.14 / 244.64 ms │     no change │
│ QQuery 40 │        25.85 / 28.28 ±2.39 / 32.06 ms │              23.36 / 28.01 ±3.12 / 33.00 ms │     no change │
│ QQuery 41 │        20.22 / 22.48 ±1.64 / 24.47 ms │              20.56 / 21.36 ±0.64 / 22.37 ms │     no change │
│ QQuery 42 │        20.22 / 20.76 ±0.44 / 21.40 ms │              19.12 / 20.01 ±0.81 / 21.50 ms │     no change │
└───────────┴───────────────────────────────────────┴─────────────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary                                          ┃            ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD)                                          │ 27782.69ms │
│ Total Time (rich-t-kid_Dictionary-encoding-Hash-optmize)   │ 27955.70ms │
│ Average Time (HEAD)                                        │   646.11ms │
│ Average Time (rich-t-kid_Dictionary-encoding-Hash-optmize) │   650.13ms │
│ Queries Faster                                             │          6 │
│ Queries Slower                                             │          3 │
│ Queries with No Change                                     │         34 │
│ Queries with Failure                                       │          0 │
└────────────────────────────────────────────────────────────┴────────────┘

Resource Usage

clickbench_partitioned — base (merge-base)

Metric Value
Wall time 140.1s
Peak memory 41.3 GiB
Avg memory 33.8 GiB
CPU user 1324.2s
CPU sys 90.7s
Peak spill 0 B

clickbench_partitioned — branch

Metric Value
Wall time 141.0s
Peak memory 41.2 GiB
Avg memory 32.4 GiB
CPU user 1326.4s
CPU sys 95.5s
Peak spill 0 B

File an issue against this benchmark runner

@Rich-T-kid
Copy link
Copy Markdown
Contributor Author

Wrote some criterion benchmarks and they reflect what the query benchmarks show. I think storing Scalar values directly in the hash map is causing performance issues. Need to profile to see heap allocations.

@Rich-T-kid
Copy link
Copy Markdown
Contributor Author

ScalarValue::try_from_array (38%) — allocating a new ScalarValue heap object for every single row
hash_one (18%) — hashing the full ScalarValue including its string contents on every row
PartialEq::eq (5.8%) — comparing ScalarValues in the HashMap on every row

lots of heap allocations are happening on the hot path (_xzm_free) - a solution to this may be to pre-allocate space for the unique values array and hashmap.
the core problem with using ScalarValue in the hot path, it's a heap allocated enum that gets created and destroyed for every single row. Should work directly with the array buffers

@Rich-T-kid
Copy link
Copy Markdown
Contributor Author

Image 4-14-26 at 1 55 PM

@Rich-T-kid
Copy link
Copy Markdown
Contributor Author

Optimization 1 — Hash caching

Instead of hashing each row's value individually, pre-compute hashes for all d distinct values in the values array once per batch and cache them in a Vec indexed by dictionary key. For a batch of n rows with d distinct values this reduces hash computations from O(n) to O(d).

Optimization 2 — Eliminate ScalarValue

Replace HashMap<ScalarValue, usize> with a structure that stores raw hashes and raw string slices pointing directly into the Arrow buffer. This eliminates per-row heap allocation (ScalarValue::try_from_array) and deallocation (drop_in_place) which the profiler shows accounts for ~60% of intern time combined.

Optimization 3 — Pre-allocate using occupancy

Use dict_array.occupancy().count_set_bits() to determine the number of truly distinct non-null values in the batch upfront and pre-allocate internal storage accordingly. This avoids incremental Vec growth during intern.

@Rich-T-kid Rich-T-kid force-pushed the rich-t-kid/Dictionary-encoding-Hash-optmize branch 3 times, most recently from 8adc7ef to c8fe15a Compare April 15, 2026 03:29
@Rich-T-kid Rich-T-kid force-pushed the rich-t-kid/Dictionary-encoding-Hash-optmize branch 2 times, most recently from 796573d to 2d7bc48 Compare April 15, 2026 15:25
let raw = Self::get_raw_bytes(values, value_idx);
if let Some((group_id, _)) = entries
.iter()
.find(|(_, stored_bytes)| raw == stored_bytes.as_slice())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this comparison for hash collisions?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, there's a chance of hash collisions where hash_array[i] == hash_array[j] but original_array[i] != original_array[j]. So when a hash already exists in the hash table, we can't just return that group id, we need to verify the actual value matches. We iterate through the vector of values that map to this hash, comparing each one to find the entry where the actual value matches, and return that group id. If no match is found it's a new group.


*/
// stores the order new unique elements are seen for self.emit()
seen_elements: Vec<Vec<u8>>, // Box<dyn Builder> doesnt provide the flexibility of building partition arrays that wed need to support emit::First(N)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe instread of a Vec<Vec<u8>> we can use another structure like ArrowBytesMap doc: https://docs.rs/datafusion/latest/datafusion/physical_expr_common/binary_map/struct.ArrowBytesMap.html , that would provide better memory tracking but I don't think the insert functions support dictionary right now

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a good idea with the current approach. The only issue I can think of is in the future when other value types are supported. ArrowBytesMap only supports,
Utf8,
Utf8View,
Binary,
BinaryView,

in my revised PR I could abstract away how seen_elements are stored into a trait comprised ofstore() retrive(N).This should make it easier for future work to integrate with ArrowDictionaryKeyType while also giving us a performance boost in the string/binary case

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also I think its worth benchmarking this. ArrowBytesMap insert api forces the client to insert arrays, not single elements. its very unlikely that the keys array is a 1-1 mapping of the values array, this means for each insertion we'll to do a Arc::new(StringArray::from(values[idx])). the overhead that comes with creating a new array (heap allocation for the buffer + heap allocation for the offsets + heap allocation for the null bitmap) might negate the benefits the specialized map provides.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also the ArrowBytesMap doesnt allow for partial takes. each call to retrieve values from ArrowBytesMap drains the entire data structure, this doesnt blend well with emit since it allows for partial takes.

@Rich-T-kid
Copy link
Copy Markdown
Contributor Author

Null handling

How nulls are currently handled
The first time a null is encountered, a sentinel byte representation is written to seen_elements as well as the hash map. This sentinel exists so that during emit(), when transform_into_array is called, we can compare the raw bytes of each entry against the sentinel to distinguish a null from a real value — ensuring that a null is appended to the output array as an actual null rather than being written out as a value. Without this, there would be no way to tell from the raw byte representation alone whether an entry represents a null or a legitimate value that happens to have the same bytes.

Why this is hard to extend to other types

This approach works for Utf8 because there exist byte sequences that are invalid UTF-8, which can serve as an unambiguous sentinel value that could never be confused with real data. However, this is difficult to extend to other types. For example, there is no equivalent concept of an 'invalid' binary buffer, since any sequence of raw bytes is valid by definition. This problem extends to numeric types as well.
One idea was to use an n+1 sized buffer to signal a null state for example, representing a null i32 as 5 bytes instead of the expected 4, where the extra byte acts as a flag to distinguish it from a valid value. However this feels clunky, adds overhead to every value comparison, and still doesn't resolve the core issue for binary types where no such invalid state can be expressed through the raw byte representation alone. This is worth its own investigation and is another reason to defer non-string types to a follow up issue.

@Rich-T-kid Rich-T-kid force-pushed the rich-t-kid/Dictionary-encoding-Hash-optmize branch 2 times, most recently from ad33d7c to 405f820 Compare April 20, 2026 02:09
@Rich-T-kid Rich-T-kid force-pushed the rich-t-kid/Dictionary-encoding-Hash-optmize branch from 405f820 to 680d0e6 Compare April 20, 2026 02:29
type GroupEntry = (usize, Option<Vec<u8>>);
pub struct GroupValuesDictionary<K: ArrowDictionaryKeyType + Send> {
// stores the order new unique elements are seen for self.emit()
seen_elements: Vec<Option<Vec<u8>>>, // Box<dyn Builder> doesnt provide the flexibility of building partition arrays that wed need to support emit::First(N)
Copy link
Copy Markdown
Contributor

@LiaCastaneda LiaCastaneda Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at other implementations, I see for nulls they keep this in a separate field and use MaybeNullBufferBuilder

canonical_indices.push(i);
}
}
// build new deduplicated values array using take
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm suprised arrow does not enforce values to be unique, theoretically when you insert a value into a dict if it was there before we only update the key array no?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea for example in group_by.slt @ 4801 there is this test->

# Table with an int column and Dict<UInt8> column:
statement ok
CREATE TABLE uint8_dict AS VALUES
(1, arrow_cast('A', 'Dictionary(UInt8, Utf8)')),
(2, arrow_cast('B', 'Dictionary(UInt8, Utf8)')),
(2, arrow_cast('A', 'Dictionary(UInt8, Utf8)')),
(4, arrow_cast('A', 'Dictionary(UInt8, Utf8)')),
(1, arrow_cast('C', 'Dictionary(UInt8, Utf8)')),
(1, arrow_cast('A', 'Dictionary(UInt8, Utf8)'));

# Group by the non-dict column
query TI rowsort
SELECT column2, count(column1) FROM uint8_dict GROUP BY column2;
----
A 4
B 1
C 1

what .Intern() receives is

keys : PrimitiveArray<UInt8>
[
  0,
  1,
  2,
  3,
  4,
  5,
]
 values : StringArray
[
  "A",
  "B",
  "A",
  "A",
  "C",
  "A",
]

Im going to go through the arrow docs to see if this is intended behavior

Comment on lines +160 to +162
if list_array.is_null(index) {
panic!() // this cannot happen. leaving this here as an invariant
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see other places in the codebase follow this pattern:

Suggested change
if list_array.is_null(index) {
panic!() // this cannot happen. leaving this here as an invariant
}
debug_assert!(!values.is_null(index));

Comment on lines +285 to +289
if cols.len() != 1 {
return Err(datafusion_common::DataFusionError::Internal(
"GroupValuesDictionary only supports single column group by".to_string(),
));
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if cols.len() != 1 {
return Err(datafusion_common::DataFusionError::Internal(
"GroupValuesDictionary only supports single column group by".to_string(),
));
}
assert_eq!(cols.len(), 1);

Copy link
Copy Markdown
Contributor

@LiaCastaneda LiaCastaneda Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it's worth adding a diagram explaining how the two-phase grouping works? It took me a while to understand it and a diagram claude made helped a lot

Input batch: DictionaryArray
  values = ["ap-south", "us-east"]   (d=2)
  keys   = [0, 1, 0, 0, 1]          (n=5)

Pre-step: hash all d values once
  value_hashes = [h("ap-south"), h("us-east")]

Pass 1 — d iterations (build key_to_group from persistent cache)
  unique_dict_value_mapping: {h("ap-south") -> (group_id=0), ...}
                                      │
  for each value in values array ─────┘
    lookup in map → key_to_group[0] = Some(0)
                    key_to_group[1] = Some(1)

Pass 2 — n iterations (assign group ids to every row)
  for each key in keys array:
    key=0 → key_to_group[0] = Some(0) → groups.push(0)
    key=1 → key_to_group[1] = Some(1) → groups.push(1)
    key=0 → key_to_group[0] = Some(0) → groups.push(0)
    key=0 → key_to_group[0] = Some(0) → groups.push(0)
    key=1 → key_to_group[1] = Some(1) → groups.push(1)

Output: groups = [0, 1, 0, 0, 1]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm currently working on this, should be done by the evening.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +652 to +654
#[cfg(test)]
mod basic_functionality {
use super::*;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These cases should already be covered by the existing sqllogictest files -- maybe we can keep only the ones with tricky edge cases that sqllogictests don't guarantee to cover, wdyt?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes sense. I'll remove a majority of the test cases besides the null edge cases test ,regression test. like you mentioned other cases should be caught by existing test.

@gabotechs
Copy link
Copy Markdown
Contributor

run benchmarks

@adriangbot
Copy link
Copy Markdown

Benchmark for this request failed.

Last 20 lines of output:

Click to expand
 * [new tag]             48.0.0-rc1     -> 48.0.0-rc1
 * [new tag]             48.0.0-rc2     -> 48.0.0-rc2
 * [new tag]             5.0.0          -> 5.0.0
 * [new tag]             5.0.0-rc1      -> 5.0.0-rc1
 * [new tag]             5.0.0-rc3      -> 5.0.0-rc3
 * [new tag]             6.0.0          -> 6.0.0
 * [new tag]             6.0.0-rc0      -> 6.0.0-rc0
 * [new tag]             7.0.0          -> 7.0.0
 * [new tag]             7.0.0-rc2      -> 7.0.0-rc2
 * [new tag]             8.0.0          -> 8.0.0
 * [new tag]             8.0.0-rc1      -> 8.0.0-rc1
 * [new tag]             8.0.0-rc2      -> 8.0.0-rc2
 * [new tag]             9.0.0          -> 9.0.0
 * [new tag]             9.0.0-rc1      -> 9.0.0-rc1
 * [new tag]             ballista-0.5.0 -> ballista-0.5.0
 * [new tag]             ballista-0.6.0 -> ballista-0.6.0
 * [new tag]             ballista-0.7.0 -> ballista-0.7.0
 * [new tag]             python-0.3.0   -> python-0.3.0
 * [new tag]             python-0.4.0   -> python-0.4.0
Switched to branch 'rich-t-kid/Dictionary-encoding-Hash-optmize'

File an issue against this benchmark runner

2 similar comments
@adriangbot
Copy link
Copy Markdown

Benchmark for this request failed.

Last 20 lines of output:

Click to expand
 * [new tag]             48.0.0-rc1     -> 48.0.0-rc1
 * [new tag]             48.0.0-rc2     -> 48.0.0-rc2
 * [new tag]             5.0.0          -> 5.0.0
 * [new tag]             5.0.0-rc1      -> 5.0.0-rc1
 * [new tag]             5.0.0-rc3      -> 5.0.0-rc3
 * [new tag]             6.0.0          -> 6.0.0
 * [new tag]             6.0.0-rc0      -> 6.0.0-rc0
 * [new tag]             7.0.0          -> 7.0.0
 * [new tag]             7.0.0-rc2      -> 7.0.0-rc2
 * [new tag]             8.0.0          -> 8.0.0
 * [new tag]             8.0.0-rc1      -> 8.0.0-rc1
 * [new tag]             8.0.0-rc2      -> 8.0.0-rc2
 * [new tag]             9.0.0          -> 9.0.0
 * [new tag]             9.0.0-rc1      -> 9.0.0-rc1
 * [new tag]             ballista-0.5.0 -> ballista-0.5.0
 * [new tag]             ballista-0.6.0 -> ballista-0.6.0
 * [new tag]             ballista-0.7.0 -> ballista-0.7.0
 * [new tag]             python-0.3.0   -> python-0.3.0
 * [new tag]             python-0.4.0   -> python-0.4.0
Switched to branch 'rich-t-kid/Dictionary-encoding-Hash-optmize'

File an issue against this benchmark runner

@adriangbot
Copy link
Copy Markdown

Benchmark for this request failed.

Last 20 lines of output:

Click to expand
 * [new tag]             48.0.0-rc1     -> 48.0.0-rc1
 * [new tag]             48.0.0-rc2     -> 48.0.0-rc2
 * [new tag]             5.0.0          -> 5.0.0
 * [new tag]             5.0.0-rc1      -> 5.0.0-rc1
 * [new tag]             5.0.0-rc3      -> 5.0.0-rc3
 * [new tag]             6.0.0          -> 6.0.0
 * [new tag]             6.0.0-rc0      -> 6.0.0-rc0
 * [new tag]             7.0.0          -> 7.0.0
 * [new tag]             7.0.0-rc2      -> 7.0.0-rc2
 * [new tag]             8.0.0          -> 8.0.0
 * [new tag]             8.0.0-rc1      -> 8.0.0-rc1
 * [new tag]             8.0.0-rc2      -> 8.0.0-rc2
 * [new tag]             9.0.0          -> 9.0.0
 * [new tag]             9.0.0-rc1      -> 9.0.0-rc1
 * [new tag]             ballista-0.5.0 -> ballista-0.5.0
 * [new tag]             ballista-0.6.0 -> ballista-0.6.0
 * [new tag]             ballista-0.7.0 -> ballista-0.7.0
 * [new tag]             python-0.3.0   -> python-0.3.0
 * [new tag]             python-0.4.0   -> python-0.4.0
Switched to branch 'rich-t-kid/Dictionary-encoding-Hash-optmize'

File an issue against this benchmark runner

Copy link
Copy Markdown
Contributor

@gabotechs gabotechs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not reviewed this PR in depth, but 3500 lines of new code sounds like unreasonably too many for what this PR has to offer from a feature standpoint.

One of the things I notice is that the code overall looks a bit too LLM-y:

  • An unreasonable amount of tests (3000 lines of tests for testing 400 lines of code)
  • Comments that just state what the code is doing
  • Over-use of highly complex macros, when avoiding them can give a much simpler implementation
  • Not using DataFusion error builders and instead construct raw errors with fully qualified paths

One advice for getting some interest from committers for prioritizing reviewing this PR, would be significantly shrink its size, and reduce in complexity as much as possible. If there's an opportunity to split the PR in multiple chunks that can be independently shipped, that's also welcomed.

@Rich-T-kid Rich-T-kid force-pushed the rich-t-kid/Dictionary-encoding-Hash-optmize branch 2 times, most recently from e0d3aaa to b8165d3 Compare April 20, 2026 21:07
let raw = Self::get_raw_bytes(child, i);
bytes.extend_from_slice(&(raw.len() as i32).to_ne_bytes());
bytes.extend_from_slice(&raw);
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

store as u64 to account for Largeutf8 taking up 64 bits

@Rich-T-kid Rich-T-kid force-pushed the rich-t-kid/Dictionary-encoding-Hash-optmize branch from b8165d3 to f8ff947 Compare April 20, 2026 21:15
@github-actions github-actions bot added the sqllogictest SQL Logic Tests (.slt) label Apr 20, 2026
@Rich-T-kid Rich-T-kid force-pushed the rich-t-kid/Dictionary-encoding-Hash-optmize branch from f8ff947 to 81998dc Compare April 20, 2026 21:17
@github-actions github-actions bot removed the sqllogictest SQL Logic Tests (.slt) label Apr 20, 2026
@Rich-T-kid Rich-T-kid force-pushed the rich-t-kid/Dictionary-encoding-Hash-optmize branch from 81998dc to dd641b9 Compare April 20, 2026 21:23
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.

4 participants