Skip to content

perf: opt substr into make_scalar_function dict fast path - #25874

Open
Rich-T-kid wants to merge 6 commits into
apache:mainfrom
Rich-T-kid:rich-T-kid/substr-dict-encoding-preservation
Open

Rich-T-kid wants to merge 6 commits into
apache:mainfrom
Rich-T-kid:rich-T-kid/substr-dict-encoding-preservation

Conversation

@Rich-T-kid

@Rich-T-kid Rich-T-kid commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Closes #26170.

Rationale for this change

substr decoded dictionary-encoded strings before evaluation. This did unnecessary per-row work and lost the dictionary encoding in the result.

What changes are included in this PR?

Keep dictionary encoding through coercion and return type inference.

  • For scalar start and length arguments, apply substr to the dictionary values once and reuse the original keys.
  • For row-wise arguments, evaluate each distinct (value key, start, length) combination and build new dictionary keys directly.

Neither path casts the full column to strings and back.

Are these changes tested?

A unit test covers the scalar fast path. dictionary_utf8.slt covers literal and runtime scalar arguments, scalar dictionary inputs, nullable row-wise arguments, and repeated input values that produce different outputs. It also verifies that the result remains dictionary-encoded.

Are there any user-facing changes?

Dictionary inputs now produce dictionary-encoded results. The string values are unchanged.

@github-actions github-actions Bot added the functions Changes to functions implementation label Sep 29, 2026
@Rich-T-kid
Rich-T-kid force-pushed the rich-T-kid/substr-dict-encoding-preservation branch from 71ad106 to 0f9e4cd Compare September 29, 2026 13:26
@Rich-T-kid

Copy link
Copy Markdown
Contributor Author

will wait until we can enable the dictionary flag to run the benchmarks. This should have a significant affect on query performance

@Rich-T-kid
Rich-T-kid force-pushed the rich-T-kid/substr-dict-encoding-preservation branch from 0f9e4cd to 497b608 Compare October 9, 2026 20:22
@github-actions github-actions Bot added the sqllogictest SQL Logic Tests (.slt) label Oct 9, 2026
@Rich-T-kid
Rich-T-kid force-pushed the rich-T-kid/substr-dict-encoding-preservation branch 2 times, most recently from 6318fb4 to d172c89 Compare October 9, 2026 20:43
For scalar start/length, substr can run over the dict's unique values
and reuse the keys instead of casting the dict to its value type.

- Signature uses EncodingPreservation::dictionary() so coercion keeps
  the dict wrapper.
- Fast path in invoke_with_args runs substr on dict.values() and
  rewraps via with_values(..).
- Per-row start/length still materializes the dict.
- return_field_from_args declares Dictionary for the scalar case and
  the value type for the per-row case so the planner's expected schema
  matches runtime output.

Closes apache#26170
@Rich-T-kid
Rich-T-kid force-pushed the rich-T-kid/substr-dict-encoding-preservation branch from d172c89 to c11a73c Compare October 9, 2026 20:50
@Rich-T-kid
Rich-T-kid marked this pull request as ready for review October 9, 2026 20:51
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 82.81250% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.80%. Comparing base (b295a67) to head (601afda).

Files with missing lines Patch % Lines
datafusion/functions/src/unicode/substr.rs 82.81% 5 Missing and 6 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #25874      +/-   ##
==========================================
- Coverage   82.80%   82.80%   -0.01%     
==========================================
  Files        1149     1149              
  Lines      452234   452296      +62     
  Branches   452234   452296      +62     
==========================================
+ Hits       374493   374527      +34     
- Misses      54946    54963      +17     
- Partials    22795    22806      +11     

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

@Rich-T-kid

Copy link
Copy Markdown
Contributor Author

run benchmark tpch
env:
DATAFUSION_RUNTIME_MEMORY_LIMIT: "32G"
baseline:
env:
DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"
changed:
env:
DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"

1 similar comment
@Rich-T-kid

Copy link
Copy Markdown
Contributor Author

run benchmark tpch
env:
DATAFUSION_RUNTIME_MEMORY_LIMIT: "32G"
baseline:
env:
DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"
changed:
env:
DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance: c4a-highcpu-48 (12 vCPU / 65 GiB) | Linux bench-c6117456708-3320-mn9ch 6.12.94+ #1 SMP Fri Aug 21 08:00:16 UTC 2026 aarch64 GNU/Linux

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  48
On-line CPU(s) list:                     0-47
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     48
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:                               3 MiB (48 instances)
L1i cache:                               3 MiB (48 instances)
L2 cache:                                96 MiB (48 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-47
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/substr-dict-encoding-preservation (601afda) to b295a67 (merge-base) diff

Run configuration
run benchmark tpch
env:
  DATAFUSION_RUNTIME_MEMORY_LIMIT: "32G"
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

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c6117457716-3321-wwxwk 6.12.94+ #1 SMP Fri Aug 21 08:00:16 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/substr-dict-encoding-preservation (601afda) to b295a67 (merge-base) diff

Run configuration
run benchmark tpch
env:
  DATAFUSION_RUNTIME_MEMORY_LIMIT: "32G"
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

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark completed (GKE) | trigger

Instance: c4a-highcpu-48 (12 vCPU / 65 GiB)

Comparing rich-T-kid/substr-dict-encoding-preservation (601afda) to b295a67 (merge-base) diff

Run configuration
run benchmark tpch
env:
  DATAFUSION_RUNTIME_MEMORY_LIMIT: "32G"
baseline:
  env:
    DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"
changed:
  env:
    DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"
CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  48
On-line CPU(s) list:                     0-47
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     48
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:                               3 MiB (48 instances)
L1i cache:                               3 MiB (48 instances)
L2 cache:                                96 MiB (48 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-47
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_substr-dict-encoding-preservation
--------------------
Benchmark tpch_sf1.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ Query     ┃     HEAD ┃ rich-T-kid_substr-dict-encoding-preservation ┃       Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ QQuery 1  │ 39.64 ms │                                     39.44 ms │    no change │
│ QQuery 2  │ 18.49 ms │                                     18.16 ms │    no change │
│ QQuery 3  │ 28.04 ms │                                     27.81 ms │    no change │
│ QQuery 4  │ 16.68 ms │                                     16.53 ms │    no change │
│ QQuery 5  │ 34.63 ms │                                     34.69 ms │    no change │
│ QQuery 6  │ 12.92 ms │                                     12.92 ms │    no change │
│ QQuery 7  │ 41.52 ms │                                     42.10 ms │    no change │
│ QQuery 8  │ 40.63 ms │                                     40.28 ms │    no change │
│ QQuery 9  │ 50.19 ms │                                     49.98 ms │    no change │
│ QQuery 10 │ 57.91 ms │                                     58.22 ms │    no change │
│ QQuery 11 │ 13.01 ms │                                     12.97 ms │    no change │
│ QQuery 12 │ 20.31 ms │                                     20.26 ms │    no change │
│ QQuery 13 │ 45.08 ms │                                     44.88 ms │    no change │
│ QQuery 14 │ 23.55 ms │                                     23.60 ms │    no change │
│ QQuery 15 │ 29.97 ms │                                     29.98 ms │    no change │
│ QQuery 16 │ 13.51 ms │                                     13.42 ms │    no change │
│ QQuery 17 │ 73.25 ms │                                     73.18 ms │    no change │
│ QQuery 18 │ 68.13 ms │                                     67.05 ms │    no change │
│ QQuery 19 │ 29.02 ms │                                     28.86 ms │    no change │
│ QQuery 20 │ 33.43 ms │                                     33.02 ms │    no change │
│ QQuery 21 │ 51.42 ms │                                     50.69 ms │    no change │
│ QQuery 22 │ 14.90 ms │                                     21.29 ms │ 1.43x slower │
└───────────┴──────────┴──────────────────────────────────────────────┴──────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┓
┃ Benchmark Summary                                           ┃          ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━┩
│ Total Time (HEAD)                                           │ 756.23ms │
│ Total Time (rich-T-kid_substr-dict-encoding-preservation)   │ 759.32ms │
│ Average Time (HEAD)                                         │  34.37ms │
│ Average Time (rich-T-kid_substr-dict-encoding-preservation) │  34.51ms │
│ Queries Faster                                              │        0 │
│ Queries Slower                                              │        1 │
│ Queries with No Change                                      │       21 │
│ Queries with Failure                                        │        0 │
└─────────────────────────────────────────────────────────────┴──────────┘

Distribution per query (min / mean ±stddev / max):

Comparing HEAD and rich-T-kid_substr-dict-encoding-preservation
--------------------
Benchmark tpch_sf1.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ Query     ┃                           HEAD ┃ rich-T-kid_substr-dict-encoding-preservation ┃       Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ QQuery 1  │ 39.64 / 40.33 ±1.14 / 42.60 ms │               39.44 / 40.19 ±1.02 / 42.20 ms │    no change │
│ QQuery 2  │ 18.49 / 18.93 ±0.39 / 19.63 ms │               18.16 / 18.60 ±0.35 / 19.23 ms │    no change │
│ QQuery 3  │ 28.04 / 28.31 ±0.20 / 28.58 ms │               27.81 / 27.95 ±0.16 / 28.24 ms │    no change │
│ QQuery 4  │ 16.68 / 16.80 ±0.16 / 17.11 ms │               16.53 / 16.77 ±0.21 / 17.14 ms │    no change │
│ QQuery 5  │ 34.63 / 34.84 ±0.11 / 34.94 ms │               34.69 / 34.82 ±0.10 / 34.97 ms │    no change │
│ QQuery 6  │ 12.92 / 13.15 ±0.14 / 13.36 ms │               12.92 / 13.09 ±0.11 / 13.25 ms │    no change │
│ QQuery 7  │ 41.52 / 42.28 ±0.43 / 42.68 ms │               42.10 / 42.66 ±0.59 / 43.73 ms │    no change │
│ QQuery 8  │ 40.63 / 40.96 ±0.26 / 41.37 ms │               40.28 / 40.60 ±0.22 / 40.80 ms │    no change │
│ QQuery 9  │ 50.19 / 51.01 ±0.56 / 51.91 ms │               49.98 / 50.73 ±0.50 / 51.53 ms │    no change │
│ QQuery 10 │ 57.91 / 58.10 ±0.13 / 58.25 ms │               58.22 / 58.52 ±0.24 / 58.80 ms │    no change │
│ QQuery 11 │ 13.01 / 13.36 ±0.32 / 13.96 ms │               12.97 / 13.26 ±0.25 / 13.62 ms │    no change │
│ QQuery 12 │ 20.31 / 20.68 ±0.21 / 20.87 ms │               20.26 / 20.64 ±0.27 / 21.09 ms │    no change │
│ QQuery 13 │ 45.08 / 45.80 ±1.10 / 47.98 ms │               44.88 / 45.21 ±0.23 / 45.56 ms │    no change │
│ QQuery 14 │ 23.55 / 23.78 ±0.16 / 24.06 ms │               23.60 / 23.80 ±0.26 / 24.30 ms │    no change │
│ QQuery 15 │ 29.97 / 30.10 ±0.09 / 30.22 ms │               29.98 / 30.12 ±0.13 / 30.30 ms │    no change │
│ QQuery 16 │ 13.51 / 13.70 ±0.13 / 13.93 ms │               13.42 / 13.67 ±0.14 / 13.83 ms │    no change │
│ QQuery 17 │ 73.25 / 74.17 ±0.61 / 74.98 ms │               73.18 / 73.84 ±0.51 / 74.73 ms │    no change │
│ QQuery 18 │ 68.13 / 71.57 ±2.89 / 74.94 ms │               67.05 / 70.65 ±2.69 / 74.98 ms │    no change │
│ QQuery 19 │ 29.02 / 29.23 ±0.25 / 29.71 ms │               28.86 / 29.29 ±0.46 / 30.17 ms │    no change │
│ QQuery 20 │ 33.43 / 33.59 ±0.17 / 33.91 ms │               33.02 / 33.30 ±0.17 / 33.54 ms │    no change │
│ QQuery 21 │ 51.42 / 51.98 ±0.39 / 52.57 ms │               50.69 / 51.38 ±0.59 / 52.34 ms │    no change │
│ QQuery 22 │ 14.90 / 15.39 ±0.31 / 15.88 ms │               21.29 / 21.50 ±0.13 / 21.68 ms │ 1.40x slower │
└───────────┴────────────────────────────────┴──────────────────────────────────────────────┴──────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┓
┃ Benchmark Summary                                           ┃          ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━┩
│ Total Time (HEAD)                                           │ 768.07ms │
│ Total Time (rich-T-kid_substr-dict-encoding-preservation)   │ 770.61ms │
│ Average Time (HEAD)                                         │  34.91ms │
│ Average Time (rich-T-kid_substr-dict-encoding-preservation) │  35.03ms │
│ Queries Faster                                              │        0 │
│ Queries Slower                                              │        1 │
│ Queries with No Change                                      │       21 │
│ Queries with Failure                                        │        0 │
└─────────────────────────────────────────────────────────────┴──────────┘

Memory Pool Peaks

Peak MemoryPool reservation per query — what DataFusion's accounting believes it reserved. Recorded only for benchmarks that write a results JSON (the dfbench suites, and the suites bench.sh runs through the Criterion SQL harness since #25644), and only under a memory limit: DATAFUSION_RUNTIME_MEMORY_LIMIT, or one the suite sets itself.

Base: b295a67 (merge-base) | Changed: rich-T-kid/substr-dict-encoding-preservation

tpch — tpch_sf1

Query Base Changed Change
Query 1 40.0 MiB 30.1 MiB -24.8%
Query 2 23.1 MiB 23.1 MiB +0.0%
Query 3 11.5 MiB 11.5 MiB +0.0%
Query 4 22.2 MiB 22.2 MiB -0.0%
Query 5 42.8 MiB 32.9 MiB -23.1%
Query 6 832 B 832 B +0.0%
Query 7 107.6 MiB 107.6 MiB +0.0%
Query 8 34.1 MiB 33.1 MiB -3.0%
Query 9 155.6 MiB 155.6 MiB +0.0%
Query 10 63.8 MiB 62.9 MiB -1.4%
Query 11 60.8 MiB 51.5 MiB -15.2%
Query 12 10.2 MiB 20.1 MiB +96.8%
Query 13 62.3 MiB 52.3 MiB -16.0%
Query 14 7.7 MiB 7.7 MiB +0.0%
Query 15 12.8 MiB 12.8 MiB -0.0%
Query 16 33.2 MiB 33.2 MiB +0.0%
Query 17 141.3 MiB 141.3 MiB +0.0%
Query 18 123.0 MiB 119.4 MiB -2.9%
Query 19 228.6 KiB 228.6 KiB +0.0%
Query 20 60.7 MiB 60.6 MiB -0.2%
Query 21 37.7 MiB 37.7 MiB +0.0%
Query 22 31.8 MiB 31.6 MiB -0.8%

Pool accounting vs. process RSS

Max pool peak is the largest reservation any single query in the run reached; peak RSS covers the whole invocation, including data loading and allocator retention, and the two high-water marks need not coincide in time. The gap is therefore an upper bound on what the pool did not account for, not a measurement of it.

Benchmark Side Max pool peak Peak RSS Gap RSS / pool
tpch base (b295a67 (merge-base)) 155.6 MiB 1.0 GiB 877.2 MiB 6.6×
tpch changed (rich-T-kid/substr-dict-encoding-preservation) 155.6 MiB 1.1 GiB 984.2 MiB 7.3×
Resource Usage

tpch — base (merge-base)

Metric Value
Wall time 5.0s
Peak memory 1.0 GiB
Avg memory 453.4 MiB
CPU user 20.5s
CPU sys 1.5s
Peak spill 0 B

tpch — branch

Metric Value
Wall time 5.0s
Peak memory 1.1 GiB
Avg memory 469.9 MiB
CPU user 20.7s
CPU sys 1.3s
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)

Comparing rich-T-kid/substr-dict-encoding-preservation (601afda) to b295a67 (merge-base) diff

Run configuration
run benchmark tpch
env:
  DATAFUSION_RUNTIME_MEMORY_LIMIT: "32G"
baseline:
  env:
    DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"
changed:
  env:
    DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"
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_substr-dict-encoding-preservation
--------------------
Benchmark tpch_sf1.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query     ┃     HEAD ┃ rich-T-kid_substr-dict-encoding-preservation ┃        Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 1  │ 41.23 ms │                                     41.12 ms │     no change │
│ QQuery 2  │ 19.66 ms │                                     19.57 ms │     no change │
│ QQuery 3  │ 29.55 ms │                                     29.23 ms │     no change │
│ QQuery 4  │ 17.77 ms │                                     17.68 ms │     no change │
│ QQuery 5  │ 37.27 ms │                                     36.90 ms │     no change │
│ QQuery 6  │ 13.97 ms │                                     13.75 ms │     no change │
│ QQuery 7  │ 44.40 ms │                                     43.51 ms │     no change │
│ QQuery 8  │ 41.87 ms │                                     41.94 ms │     no change │
│ QQuery 9  │ 52.67 ms │                                     51.75 ms │     no change │
│ QQuery 10 │ 61.01 ms │                                     60.17 ms │     no change │
│ QQuery 11 │ 14.28 ms │                                     14.08 ms │     no change │
│ QQuery 12 │ 22.10 ms │                                     21.31 ms │     no change │
│ QQuery 13 │ 48.74 ms │                                     47.17 ms │     no change │
│ QQuery 14 │ 24.78 ms │                                     24.46 ms │     no change │
│ QQuery 15 │ 32.27 ms │                                     31.52 ms │     no change │
│ QQuery 16 │ 14.13 ms │                                     14.13 ms │     no change │
│ QQuery 17 │ 78.24 ms │                                     74.91 ms │     no change │
│ QQuery 18 │ 69.09 ms │                                     69.66 ms │     no change │
│ QQuery 19 │ 30.52 ms │                                     29.92 ms │     no change │
│ QQuery 20 │ 35.05 ms │                                     33.94 ms │     no change │
│ QQuery 21 │ 56.17 ms │                                     53.03 ms │ +1.06x faster │
│ QQuery 22 │ 16.71 ms │                                     22.02 ms │  1.32x slower │
└───────────┴──────────┴──────────────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┓
┃ Benchmark Summary                                           ┃          ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━┩
│ Total Time (HEAD)                                           │ 801.48ms │
│ Total Time (rich-T-kid_substr-dict-encoding-preservation)   │ 791.77ms │
│ Average Time (HEAD)                                         │  36.43ms │
│ Average Time (rich-T-kid_substr-dict-encoding-preservation) │  35.99ms │
│ Queries Faster                                              │        1 │
│ Queries Slower                                              │        1 │
│ Queries with No Change                                      │       20 │
│ Queries with Failure                                        │        0 │
└─────────────────────────────────────────────────────────────┴──────────┘

Distribution per query (min / mean ±stddev / max):

Comparing HEAD and rich-T-kid_substr-dict-encoding-preservation
--------------------
Benchmark tpch_sf1.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query     ┃                           HEAD ┃ rich-T-kid_substr-dict-encoding-preservation ┃        Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 1  │ 41.23 / 41.95 ±1.04 / 44.01 ms │               41.12 / 41.87 ±0.78 / 43.33 ms │     no change │
│ QQuery 2  │ 19.66 / 20.37 ±0.85 / 21.97 ms │               19.57 / 19.87 ±0.25 / 20.20 ms │     no change │
│ QQuery 3  │ 29.55 / 30.89 ±1.39 / 32.98 ms │               29.23 / 29.47 ±0.21 / 29.74 ms │     no change │
│ QQuery 4  │ 17.77 / 18.37 ±0.64 / 19.59 ms │               17.68 / 18.09 ±0.48 / 19.00 ms │     no change │
│ QQuery 5  │ 37.27 / 38.17 ±0.89 / 39.66 ms │               36.90 / 37.93 ±1.26 / 40.31 ms │     no change │
│ QQuery 6  │ 13.97 / 14.17 ±0.15 / 14.42 ms │               13.75 / 13.93 ±0.11 / 14.07 ms │     no change │
│ QQuery 7  │ 44.40 / 45.34 ±0.79 / 46.45 ms │               43.51 / 45.31 ±2.21 / 49.59 ms │     no change │
│ QQuery 8  │ 41.87 / 42.32 ±0.35 / 42.70 ms │               41.94 / 42.75 ±0.67 / 43.97 ms │     no change │
│ QQuery 9  │ 52.67 / 54.13 ±1.29 / 56.38 ms │               51.75 / 52.95 ±1.32 / 54.59 ms │     no change │
│ QQuery 10 │ 61.01 / 62.85 ±1.72 / 65.08 ms │               60.17 / 60.41 ±0.23 / 60.83 ms │     no change │
│ QQuery 11 │ 14.28 / 14.55 ±0.25 / 14.94 ms │               14.08 / 14.26 ±0.12 / 14.45 ms │     no change │
│ QQuery 12 │ 22.10 / 24.42 ±2.75 / 29.69 ms │               21.31 / 21.77 ±0.24 / 22.02 ms │ +1.12x faster │
│ QQuery 13 │ 48.74 / 49.18 ±0.47 / 49.90 ms │               47.17 / 49.24 ±3.39 / 56.01 ms │     no change │
│ QQuery 14 │ 24.78 / 25.42 ±0.71 / 26.80 ms │               24.46 / 24.53 ±0.04 / 24.59 ms │     no change │
│ QQuery 15 │ 32.27 / 33.16 ±0.51 / 33.70 ms │               31.52 / 31.90 ±0.59 / 33.07 ms │     no change │
│ QQuery 16 │ 14.13 / 14.41 ±0.18 / 14.63 ms │               14.13 / 14.30 ±0.15 / 14.50 ms │     no change │
│ QQuery 17 │ 78.24 / 81.69 ±3.98 / 88.97 ms │               74.91 / 75.49 ±0.57 / 76.48 ms │ +1.08x faster │
│ QQuery 18 │ 69.09 / 73.28 ±3.27 / 77.27 ms │               69.66 / 73.18 ±1.98 / 75.21 ms │     no change │
│ QQuery 19 │ 30.52 / 31.58 ±1.05 / 33.48 ms │               29.92 / 30.09 ±0.17 / 30.41 ms │     no change │
│ QQuery 20 │ 35.05 / 35.50 ±0.38 / 36.13 ms │               33.94 / 34.45 ±0.31 / 34.77 ms │     no change │
│ QQuery 21 │ 56.17 / 58.92 ±3.10 / 64.87 ms │               53.03 / 54.95 ±1.58 / 57.06 ms │ +1.07x faster │
│ QQuery 22 │ 16.71 / 17.07 ±0.32 / 17.49 ms │               22.02 / 24.07 ±2.57 / 28.99 ms │  1.41x slower │
└───────────┴────────────────────────────────┴──────────────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┓
┃ Benchmark Summary                                           ┃          ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━┩
│ Total Time (HEAD)                                           │ 827.73ms │
│ Total Time (rich-T-kid_substr-dict-encoding-preservation)   │ 810.82ms │
│ Average Time (HEAD)                                         │  37.62ms │
│ Average Time (rich-T-kid_substr-dict-encoding-preservation) │  36.86ms │
│ Queries Faster                                              │        3 │
│ Queries Slower                                              │        1 │
│ Queries with No Change                                      │       18 │
│ Queries with Failure                                        │        0 │
└─────────────────────────────────────────────────────────────┴──────────┘

Memory Pool Peaks

Peak MemoryPool reservation per query — what DataFusion's accounting believes it reserved. Recorded only for benchmarks that write a results JSON (the dfbench suites, and the suites bench.sh runs through the Criterion SQL harness since #25644), and only under a memory limit: DATAFUSION_RUNTIME_MEMORY_LIMIT, or one the suite sets itself.

Base: b295a67 (merge-base) | Changed: rich-T-kid/substr-dict-encoding-preservation

tpch — tpch_sf1

Query Base Changed Change
Query 1 30.1 MiB 30.2 MiB +0.1%
Query 2 23.1 MiB 23.1 MiB +0.0%
Query 3 11.5 MiB 11.5 MiB +0.0%
Query 4 22.3 MiB 22.2 MiB -0.3%
Query 5 32.9 MiB 32.9 MiB -0.0%
Query 6 832 B 832 B +0.0%
Query 7 107.6 MiB 107.6 MiB +0.0%
Query 8 33.8 MiB 34.1 MiB +0.7%
Query 9 155.6 MiB 155.6 MiB +0.0%
Query 10 62.9 MiB 62.9 MiB +0.0%
Query 11 61.7 MiB 51.7 MiB -16.2%
Query 12 20.1 MiB 20.0 MiB -0.2%
Query 13 62.3 MiB 52.3 MiB -16.0%
Query 14 7.7 MiB 7.7 MiB +0.0%
Query 15 12.8 MiB 12.9 MiB +0.7%
Query 16 42.4 MiB 33.6 MiB -20.9%
Query 17 141.3 MiB 141.3 MiB +0.0%
Query 18 122.4 MiB 123.2 MiB +0.6%
Query 19 228.6 KiB 228.6 KiB +0.0%
Query 20 63.0 MiB 60.4 MiB -4.2%
Query 21 37.7 MiB 37.7 MiB +0.0%
Query 22 32.0 MiB 22.2 MiB -30.8%

Pool accounting vs. process RSS

Max pool peak is the largest reservation any single query in the run reached; peak RSS covers the whole invocation, including data loading and allocator retention, and the two high-water marks need not coincide in time. The gap is therefore an upper bound on what the pool did not account for, not a measurement of it.

Benchmark Side Max pool peak Peak RSS Gap RSS / pool
tpch base (b295a67 (merge-base)) 155.6 MiB 1.0 GiB 895.5 MiB 6.8×
tpch changed (rich-T-kid/substr-dict-encoding-preservation) 155.6 MiB 1021.7 MiB 866.1 MiB 6.6×
Resource Usage

tpch — base (merge-base)

Metric Value
Wall time 5.0s
Peak memory 1.0 GiB
Avg memory 621.2 MiB
CPU user 21.5s
CPU sys 1.6s
Peak spill 0 B

tpch — branch

Metric Value
Wall time 5.0s
Peak memory 1021.7 MiB
Avg memory 616.1 MiB
CPU user 21.2s
CPU sys 1.7s
Peak spill 0 B

File an issue against this benchmark runner

@Rich-T-kid

Copy link
Copy Markdown
Contributor Author

🤔 this got slower because the cardinaility of the column is neaer unqiue. will revisit this after we implement use defined thresholds

@jayzhan211

Copy link
Copy Markdown
Contributor

🤔 this got slower because the cardinaility of the column is neaer unqiue. will revisit this after we implement use defined thresholds

Would you mind marking this as a draft for now? Feel free to mark it ready again once it's good for another look.

@Rich-T-kid

Copy link
Copy Markdown
Contributor Author

sounds good to me

@Rich-T-kid
Rich-T-kid marked this pull request as draft October 16, 2026 15:40
@Rich-T-kid
Rich-T-kid marked this pull request as ready for review October 21, 2026 04:09
@Rich-T-kid

Copy link
Copy Markdown
Contributor Author

@jayzhan211 this should be good for review. the benchmarks show a slow down for │ QQuery 22 when dictionary encodded because the cardinality is high but we should still avoid the unnecessary cast.

@jayzhan211 jayzhan211 left a comment

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 checked the speedup locally on TPC-H SF1 with enable_rle_to_dictionary=true. substr(l_shipinstruct, 1, 4) grouped is about 3.7x faster (10–11 ms vs 37–41 ms), and Q22 is a bit slower (16–18 ms vs 15–16 ms), matching the bot. Since the config is opt-in, I'm fine with handling the high-cardinality case in the follow-up threshold work you mentioned. A note about the trade-off in the PR description would help.

Comment thread datafusion/functions/src/unicode/substr.rs Outdated
Comment thread datafusion/functions/src/unicode/substr.rs Outdated
Comment thread datafusion/sqllogictest/test_files/string/dictionary_utf8.slt
@Rich-T-kid

Copy link
Copy Markdown
Contributor Author

run benchmark tpch
env:
DATAFUSION_RUNTIME_MEMORY_LIMIT: "32G"
baseline:
env:
DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"
changed:
env:
DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"

@adriangbot

Copy link
Copy Markdown

Hi @Rich-T-kid, your benchmark configuration could not be parsed (#25874 (comment)).

Error: invalid configuration: duplicate field env``

Usage:

run benchmark <name>           # run specific benchmark(s)
run benchmarks                 # run default suite
run benchmarks <name1> <name2> # run specific benchmarks

Any benchmark name is accepted: bench.sh suite names (e.g. tpch, clickbench_partitioned, wide_schema) and Criterion bench targets (e.g. sql_planner) are resolved automatically. A name that matches neither fails on the runner.

Per-side configuration (run benchmark tpch followed by):

env:
# shared env is inherited by BOTH the build and the run, so build
# flags go here. Builds default to no debuginfo for speed; opt back
# in for hung-job gdb dumps and cap jobs to stay within memory:
CARGO_PROFILE_RELEASE_DEBUG: "1"
CARGO_BUILD_JOBS: "1"
baseline:
ref: v45.0.0
env:
# per-side env only reaches the benchmark run, not the build
DATAFUSION_RUNTIME_MEMORY_LIMIT: 1G
changed:
ref: v46.0.0
env:
DATAFUSION_RUNTIME_MEMORY_LIMIT: 2G

Pod size and CPU architecture (each key is optional and keeps the default when absent; requests and limits are set equal):

resources:
cpu: "16"
memory: "128Gi"
arch: arm64          # arm64 or amd64

Independent benchmark shards (optional; 1–8, default 1):

run benchmark arrow_writer
shards: 4

Each shard builds both revisions, measures its assigned cases, and posts its own report. Resources and the job deadline apply per shard. DataFusion requests specifying a shard count greater than 1 fail with an error. Omit shards or use shards: 1 for DataFusion.


File an issue against this benchmark runner

@Rich-T-kid

Copy link
Copy Markdown
Contributor Author

run benchmark tpch
env:
DATAFUSION_RUNTIME_MEMORY_LIMIT: "32G"
baseline:
env:
DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"
changed:
env:
DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"

@adriangbot

Copy link
Copy Markdown

Hi @Rich-T-kid, your benchmark configuration could not be parsed (#25874 (comment)).

Error: invalid configuration: duplicate field env``

Usage:

run benchmark <name>           # run specific benchmark(s)
run benchmarks                 # run default suite
run benchmarks <name1> <name2> # run specific benchmarks

Any benchmark name is accepted: bench.sh suite names (e.g. tpch, clickbench_partitioned, wide_schema) and Criterion bench targets (e.g. sql_planner) are resolved automatically. A name that matches neither fails on the runner.

Per-side configuration (run benchmark tpch followed by):

env:
# shared env is inherited by BOTH the build and the run, so build
# flags go here. Builds default to no debuginfo for speed; opt back
# in for hung-job gdb dumps and cap jobs to stay within memory:
CARGO_PROFILE_RELEASE_DEBUG: "1"
CARGO_BUILD_JOBS: "1"
baseline:
ref: v45.0.0
env:
# per-side env only reaches the benchmark run, not the build
DATAFUSION_RUNTIME_MEMORY_LIMIT: 1G
changed:
ref: v46.0.0
env:
DATAFUSION_RUNTIME_MEMORY_LIMIT: 2G

Pod size and CPU architecture (each key is optional and keeps the default when absent; requests and limits are set equal):

resources:
cpu: "16"
memory: "128Gi"
arch: arm64          # arm64 or amd64

Independent benchmark shards (optional; 1–8, default 1):

run benchmark arrow_writer
shards: 4

Each shard builds both revisions, measures its assigned cases, and posts its own report. Resources and the job deadline apply per shard. DataFusion requests specifying a shard count greater than 1 fail with an error. Omit shards or use shards: 1 for DataFusion.


File an issue against this benchmark runner

@Rich-T-kid

Copy link
Copy Markdown
Contributor Author

run benchmark tpch
env:
DATAFUSION_RUNTIME_MEMORY_LIMIT: "32G"
baseline:
env:
DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"
changed:
env:
DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"

@Rich-T-kid

Copy link
Copy Markdown
Contributor Author

@jayzhan211 I've addressed your comments as well as updated the PR description, plz take a look 🙇

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c6324274953-3734-rsr4x 6.12.94+ #1 SMP Fri Aug 21 08:00:16 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/substr-dict-encoding-preservation (3a90a9f) to b295a67 (merge-base) diff

Run configuration
run benchmark tpch
env:
  DATAFUSION_RUNTIME_MEMORY_LIMIT: "32G"
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

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark completed (GKE) | trigger

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

Comparing rich-T-kid/substr-dict-encoding-preservation (3a90a9f) to b295a67 (merge-base) diff

Run configuration
run benchmark tpch
env:
  DATAFUSION_RUNTIME_MEMORY_LIMIT: "32G"
baseline:
  env:
    DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"
changed:
  env:
    DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"
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_substr-dict-encoding-preservation
--------------------
Benchmark tpch_sf1.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ Query     ┃     HEAD ┃ rich-T-kid_substr-dict-encoding-preservation ┃       Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ QQuery 1  │ 40.87 ms │                                     40.85 ms │    no change │
│ QQuery 2  │ 19.58 ms │                                     19.34 ms │    no change │
│ QQuery 3  │ 29.18 ms │                                     29.16 ms │    no change │
│ QQuery 4  │ 17.67 ms │                                     17.62 ms │    no change │
│ QQuery 5  │ 36.71 ms │                                     36.47 ms │    no change │
│ QQuery 6  │ 13.71 ms │                                     13.91 ms │    no change │
│ QQuery 7  │ 43.51 ms │                                     43.64 ms │    no change │
│ QQuery 8  │ 42.04 ms │                                     41.71 ms │    no change │
│ QQuery 9  │ 52.71 ms │                                     51.84 ms │    no change │
│ QQuery 10 │ 61.42 ms │                                     60.78 ms │    no change │
│ QQuery 11 │ 14.45 ms │                                     14.83 ms │    no change │
│ QQuery 12 │ 21.80 ms │                                     21.68 ms │    no change │
│ QQuery 13 │ 48.09 ms │                                     48.50 ms │    no change │
│ QQuery 14 │ 24.64 ms │                                     24.75 ms │    no change │
│ QQuery 15 │ 31.34 ms │                                     31.68 ms │    no change │
│ QQuery 16 │ 14.33 ms │                                     14.50 ms │    no change │
│ QQuery 17 │ 77.26 ms │                                     78.26 ms │    no change │
│ QQuery 18 │ 71.26 ms │                                     72.03 ms │    no change │
│ QQuery 19 │ 30.35 ms │                                     30.35 ms │    no change │
│ QQuery 20 │ 35.06 ms │                                     34.66 ms │    no change │
│ QQuery 21 │ 55.21 ms │                                     55.26 ms │    no change │
│ QQuery 22 │ 16.44 ms │                                     23.11 ms │ 1.41x slower │
└───────────┴──────────┴──────────────────────────────────────────────┴──────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┓
┃ Benchmark Summary                                           ┃          ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━┩
│ Total Time (HEAD)                                           │ 797.62ms │
│ Total Time (rich-T-kid_substr-dict-encoding-preservation)   │ 804.94ms │
│ Average Time (HEAD)                                         │  36.26ms │
│ Average Time (rich-T-kid_substr-dict-encoding-preservation) │  36.59ms │
│ Queries Faster                                              │        0 │
│ Queries Slower                                              │        1 │
│ Queries with No Change                                      │       21 │
│ Queries with Failure                                        │        0 │
└─────────────────────────────────────────────────────────────┴──────────┘

Distribution per query (min / mean ±stddev / max):

Comparing HEAD and rich-T-kid_substr-dict-encoding-preservation
--------------------
Benchmark tpch_sf1.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ Query     ┃                           HEAD ┃ rich-T-kid_substr-dict-encoding-preservation ┃       Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ QQuery 1  │ 40.87 / 41.53 ±1.07 / 43.66 ms │               40.85 / 41.44 ±0.93 / 43.29 ms │    no change │
│ QQuery 2  │ 19.58 / 19.72 ±0.17 / 20.04 ms │               19.34 / 19.50 ±0.17 / 19.83 ms │    no change │
│ QQuery 3  │ 29.18 / 29.56 ±0.21 / 29.77 ms │               29.16 / 29.34 ±0.19 / 29.67 ms │    no change │
│ QQuery 4  │ 17.67 / 18.32 ±0.67 / 19.36 ms │               17.62 / 17.98 ±0.24 / 18.36 ms │    no change │
│ QQuery 5  │ 36.71 / 37.92 ±1.32 / 39.62 ms │               36.47 / 37.76 ±1.36 / 39.59 ms │    no change │
│ QQuery 6  │ 13.71 / 13.97 ±0.32 / 14.53 ms │               13.91 / 14.15 ±0.32 / 14.78 ms │    no change │
│ QQuery 7  │ 43.51 / 45.12 ±1.90 / 48.72 ms │               43.64 / 45.63 ±1.11 / 46.71 ms │    no change │
│ QQuery 8  │ 42.04 / 42.38 ±0.27 / 42.75 ms │               41.71 / 41.96 ±0.17 / 42.16 ms │    no change │
│ QQuery 9  │ 52.71 / 53.38 ±0.62 / 54.33 ms │               51.84 / 53.09 ±1.03 / 54.89 ms │    no change │
│ QQuery 10 │ 61.42 / 62.23 ±0.67 / 62.95 ms │               60.78 / 61.69 ±1.23 / 64.06 ms │    no change │
│ QQuery 11 │ 14.45 / 14.64 ±0.14 / 14.79 ms │               14.83 / 15.33 ±0.59 / 16.36 ms │    no change │
│ QQuery 12 │ 21.80 / 22.29 ±0.88 / 24.05 ms │               21.68 / 23.12 ±1.91 / 26.76 ms │    no change │
│ QQuery 13 │ 48.09 / 48.77 ±0.70 / 49.93 ms │               48.50 / 50.08 ±1.97 / 53.48 ms │    no change │
│ QQuery 14 │ 24.64 / 24.83 ±0.14 / 25.05 ms │               24.75 / 25.12 ±0.25 / 25.49 ms │    no change │
│ QQuery 15 │ 31.34 / 31.97 ±0.42 / 32.50 ms │               31.68 / 32.33 ±0.91 / 34.11 ms │    no change │
│ QQuery 16 │ 14.33 / 14.48 ±0.12 / 14.63 ms │               14.50 / 14.66 ±0.10 / 14.77 ms │    no change │
│ QQuery 17 │ 77.26 / 79.28 ±1.55 / 82.05 ms │               78.26 / 79.69 ±1.34 / 81.79 ms │    no change │
│ QQuery 18 │ 71.26 / 73.91 ±2.62 / 77.27 ms │               72.03 / 74.36 ±1.72 / 77.20 ms │    no change │
│ QQuery 19 │ 30.35 / 30.71 ±0.20 / 30.95 ms │               30.35 / 30.70 ±0.36 / 31.32 ms │    no change │
│ QQuery 20 │ 35.06 / 35.43 ±0.26 / 35.76 ms │               34.66 / 35.10 ±0.29 / 35.39 ms │    no change │
│ QQuery 21 │ 55.21 / 55.66 ±0.43 / 56.36 ms │               55.26 / 56.00 ±0.69 / 57.09 ms │    no change │
│ QQuery 22 │ 16.44 / 17.63 ±1.77 / 21.16 ms │               23.11 / 23.42 ±0.33 / 24.01 ms │ 1.33x slower │
└───────────┴────────────────────────────────┴──────────────────────────────────────────────┴──────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┓
┃ Benchmark Summary                                           ┃          ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━┩
│ Total Time (HEAD)                                           │ 813.72ms │
│ Total Time (rich-T-kid_substr-dict-encoding-preservation)   │ 822.44ms │
│ Average Time (HEAD)                                         │  36.99ms │
│ Average Time (rich-T-kid_substr-dict-encoding-preservation) │  37.38ms │
│ Queries Faster                                              │        0 │
│ Queries Slower                                              │        1 │
│ Queries with No Change                                      │       21 │
│ Queries with Failure                                        │        0 │
└─────────────────────────────────────────────────────────────┴──────────┘

Memory Pool Peaks

Peak MemoryPool reservation per query — what DataFusion's accounting believes it reserved. Recorded only for benchmarks that write a results JSON (the dfbench suites, and the suites bench.sh runs through the Criterion SQL harness since #25644), and only under a memory limit: DATAFUSION_RUNTIME_MEMORY_LIMIT, or one the suite sets itself.

Base: b295a67 (merge-base) | Changed: rich-T-kid/substr-dict-encoding-preservation

tpch — tpch_sf1

Query Base Changed Change
Query 1 20.2 MiB 30.1 MiB +49.3%
Query 2 23.1 MiB 23.1 MiB +0.0%
Query 3 11.5 MiB 11.5 MiB +0.0%
Query 4 22.3 MiB 32.2 MiB +44.5%
Query 5 32.9 MiB 42.8 MiB +30.1%
Query 6 832 B 832 B +0.0%
Query 7 107.6 MiB 107.6 MiB +0.0%
Query 8 34.6 MiB 35.5 MiB +2.6%
Query 9 155.6 MiB 155.6 MiB +0.0%
Query 10 62.9 MiB 62.9 MiB -0.1%
Query 11 61.9 MiB 61.2 MiB -1.2%
Query 12 20.1 MiB 20.0 MiB -0.2%
Query 13 62.3 MiB 52.5 MiB -15.8%
Query 14 7.7 MiB 7.7 MiB +0.0%
Query 15 12.8 MiB 12.8 MiB -0.0%
Query 16 33.4 MiB 33.2 MiB -0.6%
Query 17 141.3 MiB 141.3 MiB +0.0%
Query 18 123.3 MiB 123.4 MiB +0.1%
Query 19 228.6 KiB 228.6 KiB +0.0%
Query 20 59.5 MiB 58.5 MiB -1.7%
Query 21 37.7 MiB 37.7 MiB +0.0%
Query 22 32.0 MiB 22.1 MiB -30.8%

Pool accounting vs. process RSS

Max pool peak is the largest reservation any single query in the run reached; peak RSS covers the whole invocation, including data loading and allocator retention, and the two high-water marks need not coincide in time. The gap is therefore an upper bound on what the pool did not account for, not a measurement of it.

Benchmark Side Max pool peak Peak RSS Gap RSS / pool
tpch base (b295a67 (merge-base)) 155.6 MiB 1.0 GiB 877.5 MiB 6.6×
tpch changed (rich-T-kid/substr-dict-encoding-preservation) 155.6 MiB 1.0 GiB 916.9 MiB 6.9×
Resource Usage

tpch — base (merge-base)

Metric Value
Wall time 5.0s
Peak memory 1.0 GiB
Avg memory 610.2 MiB
CPU user 21.6s
CPU sys 1.7s
Peak spill 0 B

tpch — branch

Metric Value
Wall time 5.0s
Peak memory 1.0 GiB
Avg memory 616.8 MiB
CPU user 21.5s
CPU sys 1.8s
Peak spill 0 B

File an issue against this benchmark runner

Comment thread datafusion/functions/src/unicode/substr.rs Outdated
lyne7-sc pushed a commit to lyne7-sc/datafusion that referenced this pull request Oct 23, 2026
## Which issue does this PR close?

- N/A

## Rationale for this change

The semver check previously compared the PR against the current state of
`apache/main`, and did so by doing a full `git` checkout, including the
entire repository history. This approach has two downsides:

1. It is expensive: cloning the entire repo history takes ~9 sec
locally, versus ~0.7 for a shallow checkout.
2. There's a race condition which results in comparing the PR against
the wrong base when assessing whether the PR breaks semantic versioning.

On (2): when a PR is opened or updated, GitHub creates a test merge
commit that merges the PR into main as of that moment (T0). Now suppose
that there is a delay between the CI jobs being queued and the semver
job actually running, so that when the semver job runs, `HEAD` is now at
T1. The previous version of this check would compare the PR with T1, not
T0. For example, if a commit between T0 and T1 added a new API, the
semver check would claim that the PR _removes_ that API.

Fix this by comparing the merge commit with its first parent instead,
which is T0 in the notation above.

## What changes are included in this PR?

See above.

## What is the testing strategy for this PR?

CI / dev tooling change only. Tested locally on six open PRs (apache#26565,
apache#26000, apache#26582, apache#25874, apache#26534, apache#26576): fetched each PR's merge ref at
depth 2 and ran both steps against `HEAD^1`. The crate lists and semver
results match CI, including two PRs that branched 66 and 154 commits
behind main.

## Are there any user-facing changes?

No.
Per-row start and length values can create more dictionary entries than Int8, UInt8, Int16, or UInt16 keys can represent. Decode those inputs while retaining dictionary preservation for wide key types, and cover the Int8 overflow regression in the dictionary SQLLogicTest.
}
}

fn dictionary_substr<K: ArrowDictionaryKeyType>(

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.

Thanks for the Int8 fix, the repro passes now. One more thing on this row-wise path: per-row start/length rarely repeat as combinations, so the HashMap dedup mostly adds hashing cost. On 8192 rows (same release build), substr(dict, start_array) is 1.5x slower than cast to Utf8 + substr (what main does) with 16 distinct values, and 5.7x slower with 8192.

Only wide key types reach this function, so one key per row always fits. Decoding and wrapping the result with identity keys runs at 1.01-1.03x of the decode path, and the existing unit tests and string/ slt still pass:

fn dictionary_substr<K: ArrowDictionaryKeyType>(
    dictionary: &DictionaryArray<K>,
    args: &[ArrayRef],
) -> Result<ArrayRef> {
    // Only wide key types reach here, so one key per row always fits.
    let decoded = take(dictionary.values().as_ref(), dictionary.keys(), None)?;
    let mut row_args = vec![decoded];
    row_args.extend(args.iter().cloned());
    let values = substr(&row_args)?;
    let keys = (0..values.len())
        .map(|i| K::Native::from_usize(i).unwrap())
        .collect::<Vec<_>>();
    let keys = PrimitiveArray::<K>::new(keys.into(), values.nulls().cloned());
    Ok(Arc::new(DictionaryArray::<K>::try_new(keys, values)?))
}

Decode wide-key dictionaries once and wrap the row results with identity keys, avoiding HashMap overhead when start and length combinations rarely repeat.
@Rich-T-kid

Copy link
Copy Markdown
Contributor Author

run benchmark tpch
env:
DATAFUSION_RUNTIME_MEMORY_LIMIT: "32G"
baseline:
env:
DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"
changed:
env:
DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"

@adriangbot

Copy link
Copy Markdown

Hi @Rich-T-kid, your benchmark configuration could not be parsed (#25874 (comment)).

Error: invalid configuration: duplicate field env``

Usage:

run benchmark <name>           # run specific benchmark(s)
run benchmarks                 # run default suite
run benchmarks <name1> <name2> # run specific benchmarks

Any benchmark name is accepted: bench.sh suite names (e.g. tpch, clickbench_partitioned, wide_schema) and Criterion bench targets (e.g. sql_planner) are resolved automatically. A name that matches neither fails on the runner.

Per-side configuration (run benchmark tpch followed by):

env:
# shared env is inherited by BOTH the build and the run, so build
# flags go here. Builds default to no debuginfo for speed; opt back
# in for hung-job gdb dumps and cap jobs to stay within memory:
CARGO_PROFILE_RELEASE_DEBUG: "1"
CARGO_BUILD_JOBS: "1"
baseline:
ref: v45.0.0
env:
# per-side env only reaches the benchmark run, not the build
DATAFUSION_RUNTIME_MEMORY_LIMIT: 1G
changed:
ref: v46.0.0
env:
DATAFUSION_RUNTIME_MEMORY_LIMIT: 2G

Pod size and CPU architecture (each key is optional and keeps the default when absent; requests and limits are set equal):

resources:
cpu: "16"
memory: "128Gi"
arch: arm64          # arm64 or amd64

Independent benchmark shards (optional; 1–8, default 1):

run benchmark arrow_writer
shards: 4

Each shard builds both revisions, measures its assigned cases, and posts its own report. Resources and the job deadline apply per shard. DataFusion requests specifying a shard count greater than 1 fail with an error. Omit shards or use shards: 1 for DataFusion.


File an issue against this benchmark runner

@Rich-T-kid

Copy link
Copy Markdown
Contributor Author

run benchmark tpch
env:
DATAFUSION_RUNTIME_MEMORY_LIMIT: "32G"
baseline:
env:
DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"
changed:
env:
DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c6368797521-3797-grr8d 6.12.105+ #1 SMP Sat Sep 5 07:54:11 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/substr-dict-encoding-preservation (fbe0593) to b295a67 (merge-base) diff

Run configuration
run benchmark tpch
env:
  DATAFUSION_RUNTIME_MEMORY_LIMIT: "32G"
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

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark completed (GKE) | trigger

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

Comparing rich-T-kid/substr-dict-encoding-preservation (fbe0593) to b295a67 (merge-base) diff

Run configuration
run benchmark tpch
env:
  DATAFUSION_RUNTIME_MEMORY_LIMIT: "32G"
baseline:
  env:
    DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"
changed:
  env:
    DATAFUSION_EXECUTION_PARQUET_ENABLE_RLE_TO_DICTIONARY: "true"
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_substr-dict-encoding-preservation
--------------------
Benchmark tpch_sf1.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ Query     ┃     HEAD ┃ rich-T-kid_substr-dict-encoding-preservation ┃       Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ QQuery 1  │ 39.97 ms │                                     40.33 ms │    no change │
│ QQuery 2  │ 19.05 ms │                                     19.32 ms │    no change │
│ QQuery 3  │ 28.35 ms │                                     28.57 ms │    no change │
│ QQuery 4  │ 17.17 ms │                                     17.58 ms │    no change │
│ QQuery 5  │ 35.66 ms │                                     36.06 ms │    no change │
│ QQuery 6  │ 13.74 ms │                                     13.64 ms │    no change │
│ QQuery 7  │ 41.32 ms │                                     42.99 ms │    no change │
│ QQuery 8  │ 41.23 ms │                                     41.26 ms │    no change │
│ QQuery 9  │ 52.85 ms │                                     50.25 ms │    no change │
│ QQuery 10 │ 59.51 ms │                                     59.75 ms │    no change │
│ QQuery 11 │ 13.69 ms │                                     13.96 ms │    no change │
│ QQuery 12 │ 21.26 ms │                                     20.94 ms │    no change │
│ QQuery 13 │ 46.54 ms │                                     47.03 ms │    no change │
│ QQuery 14 │ 24.34 ms │                                     24.25 ms │    no change │
│ QQuery 15 │ 31.05 ms │                                     31.27 ms │    no change │
│ QQuery 16 │ 13.94 ms │                                     14.23 ms │    no change │
│ QQuery 17 │ 74.97 ms │                                     75.72 ms │    no change │
│ QQuery 18 │ 69.80 ms │                                     69.74 ms │    no change │
│ QQuery 19 │ 30.03 ms │                                     29.75 ms │    no change │
│ QQuery 20 │ 34.47 ms │                                     34.34 ms │    no change │
│ QQuery 21 │ 54.70 ms │                                     54.11 ms │    no change │
│ QQuery 22 │ 15.96 ms │                                     21.93 ms │ 1.37x slower │
└───────────┴──────────┴──────────────────────────────────────────────┴──────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┓
┃ Benchmark Summary                                           ┃          ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━┩
│ Total Time (HEAD)                                           │ 779.61ms │
│ Total Time (rich-T-kid_substr-dict-encoding-preservation)   │ 787.03ms │
│ Average Time (HEAD)                                         │  35.44ms │
│ Average Time (rich-T-kid_substr-dict-encoding-preservation) │  35.77ms │
│ Queries Faster                                              │        0 │
│ Queries Slower                                              │        1 │
│ Queries with No Change                                      │       21 │
│ Queries with Failure                                        │        0 │
└─────────────────────────────────────────────────────────────┴──────────┘

Distribution per query (min / mean ±stddev / max):

Comparing HEAD and rich-T-kid_substr-dict-encoding-preservation
--------------------
Benchmark tpch_sf1.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ Query     ┃                           HEAD ┃ rich-T-kid_substr-dict-encoding-preservation ┃       Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ QQuery 1  │ 39.97 / 41.37 ±1.19 / 43.06 ms │               40.33 / 41.82 ±1.19 / 43.02 ms │    no change │
│ QQuery 2  │ 19.05 / 19.67 ±0.99 / 21.64 ms │               19.32 / 19.53 ±0.37 / 20.26 ms │    no change │
│ QQuery 3  │ 28.35 / 28.94 ±0.47 / 29.72 ms │               28.57 / 28.80 ±0.14 / 28.96 ms │    no change │
│ QQuery 4  │ 17.17 / 17.77 ±0.59 / 18.88 ms │               17.58 / 17.90 ±0.57 / 19.05 ms │    no change │
│ QQuery 5  │ 35.66 / 36.68 ±1.40 / 39.31 ms │               36.06 / 36.37 ±0.21 / 36.71 ms │    no change │
│ QQuery 6  │ 13.74 / 13.81 ±0.06 / 13.92 ms │               13.64 / 14.12 ±0.68 / 15.46 ms │    no change │
│ QQuery 7  │ 41.32 / 42.97 ±1.65 / 45.24 ms │               42.99 / 44.61 ±1.23 / 45.84 ms │    no change │
│ QQuery 8  │ 41.23 / 42.46 ±1.03 / 44.32 ms │               41.26 / 41.57 ±0.29 / 42.07 ms │    no change │
│ QQuery 9  │ 52.85 / 53.64 ±0.64 / 54.57 ms │               50.25 / 52.50 ±1.97 / 55.47 ms │    no change │
│ QQuery 10 │ 59.51 / 60.83 ±1.94 / 64.67 ms │               59.75 / 60.51 ±0.54 / 61.10 ms │    no change │
│ QQuery 11 │ 13.69 / 13.96 ±0.25 / 14.37 ms │               13.96 / 14.11 ±0.16 / 14.32 ms │    no change │
│ QQuery 12 │ 21.26 / 21.70 ±0.37 / 22.33 ms │               20.94 / 21.42 ±0.42 / 22.06 ms │    no change │
│ QQuery 13 │ 46.54 / 48.09 ±1.74 / 51.33 ms │               47.03 / 48.50 ±1.88 / 52.11 ms │    no change │
│ QQuery 14 │ 24.34 / 24.55 ±0.17 / 24.85 ms │               24.25 / 24.52 ±0.19 / 24.73 ms │    no change │
│ QQuery 15 │ 31.05 / 31.44 ±0.55 / 32.53 ms │               31.27 / 31.47 ±0.21 / 31.86 ms │    no change │
│ QQuery 16 │ 13.94 / 14.06 ±0.09 / 14.18 ms │               14.23 / 14.33 ±0.09 / 14.49 ms │    no change │
│ QQuery 17 │ 74.97 / 75.75 ±0.44 / 76.33 ms │               75.72 / 77.28 ±1.14 / 78.52 ms │    no change │
│ QQuery 18 │ 69.80 / 72.44 ±2.27 / 75.62 ms │               69.74 / 71.87 ±1.82 / 75.05 ms │    no change │
│ QQuery 19 │ 30.03 / 30.54 ±0.42 / 31.28 ms │               29.75 / 30.09 ±0.45 / 30.99 ms │    no change │
│ QQuery 20 │ 34.47 / 35.03 ±0.60 / 36.16 ms │               34.34 / 34.63 ±0.20 / 34.86 ms │    no change │
│ QQuery 21 │ 54.70 / 55.54 ±0.61 / 56.59 ms │               54.11 / 56.30 ±2.52 / 61.20 ms │    no change │
│ QQuery 22 │ 15.96 / 16.35 ±0.39 / 17.09 ms │               21.93 / 23.67 ±2.33 / 28.19 ms │ 1.45x slower │
└───────────┴────────────────────────────────┴──────────────────────────────────────────────┴──────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┓
┃ Benchmark Summary                                           ┃          ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━┩
│ Total Time (HEAD)                                           │ 797.59ms │
│ Total Time (rich-T-kid_substr-dict-encoding-preservation)   │ 805.94ms │
│ Average Time (HEAD)                                         │  36.25ms │
│ Average Time (rich-T-kid_substr-dict-encoding-preservation) │  36.63ms │
│ Queries Faster                                              │        0 │
│ Queries Slower                                              │        1 │
│ Queries with No Change                                      │       21 │
│ Queries with Failure                                        │        0 │
└─────────────────────────────────────────────────────────────┴──────────┘

Memory Pool Peaks

Peak MemoryPool reservation per query — what DataFusion's accounting believes it reserved. Recorded only for benchmarks that write a results JSON (the dfbench suites, and the suites bench.sh runs through the Criterion SQL harness since #25644), and only under a memory limit: DATAFUSION_RUNTIME_MEMORY_LIMIT, or one the suite sets itself.

Base: b295a67 (merge-base) | Changed: rich-T-kid/substr-dict-encoding-preservation

tpch — tpch_sf1

Query Base Changed Change
Query 1 30.1 MiB 40.1 MiB +33.1%
Query 2 23.1 MiB 23.1 MiB +0.0%
Query 3 11.5 MiB 11.5 MiB +0.0%
Query 4 32.2 MiB 32.2 MiB +0.0%
Query 5 32.8 MiB 32.8 MiB +0.0%
Query 6 832 B 832 B +0.0%
Query 7 107.6 MiB 107.6 MiB +0.0%
Query 8 35.5 MiB 33.3 MiB -6.4%
Query 9 155.6 MiB 155.6 MiB +0.0%
Query 10 62.9 MiB 62.9 MiB +0.1%
Query 11 71.9 MiB 41.3 MiB -42.5%
Query 12 10.2 MiB 20.1 MiB +96.8%
Query 13 62.3 MiB 52.3 MiB -16.0%
Query 14 7.7 MiB 7.7 MiB +0.0%
Query 15 12.8 MiB 12.8 MiB +0.0%
Query 16 42.8 MiB 42.4 MiB -1.1%
Query 17 141.3 MiB 142.0 MiB +0.5%
Query 18 122.3 MiB 123.2 MiB +0.8%
Query 19 228.6 KiB 228.6 KiB +0.0%
Query 20 58.7 MiB 64.8 MiB +10.5%
Query 21 37.7 MiB 37.7 MiB +0.0%
Query 22 32.2 MiB 22.0 MiB -31.7%

Pool accounting vs. process RSS

Max pool peak is the largest reservation any single query in the run reached; peak RSS covers the whole invocation, including data loading and allocator retention, and the two high-water marks need not coincide in time. The gap is therefore an upper bound on what the pool did not account for, not a measurement of it.

Benchmark Side Max pool peak Peak RSS Gap RSS / pool
tpch base (b295a67 (merge-base)) 155.6 MiB 1.0 GiB 918.7 MiB 6.9×
tpch changed (rich-T-kid/substr-dict-encoding-preservation) 155.6 MiB 1.0 GiB 876.3 MiB 6.6×
Resource Usage

tpch — base (merge-base)

Metric Value
Wall time 5.0s
Peak memory 1.0 GiB
Avg memory 516.6 MiB
CPU user 21.2s
CPU sys 1.6s
Peak spill 0 B

tpch — branch

Metric Value
Wall time 5.0s
Peak memory 1.0 GiB
Avg memory 612.4 MiB
CPU user 21.2s
CPU sys 1.7s
Peak spill 0 B

File an issue against this benchmark runner

@jayzhan211

Copy link
Copy Markdown
Contributor

QQuery 22 slows down 🤔

pub fn new() -> Self {
let string = Coercion::new_exact(TypeSignatureClass::Native(logical_string()));
let string = Coercion::new_exact(TypeSignatureClass::Native(logical_string()))
.with_encoding_preservation(EncodingPreservation::dictionary());

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.

Returning a dictionary also makes a high-cardinality GROUP BY on the result slower, with or without the guard. With enable_rle_to_dictionary=true on ClickBench, SELECT substr("SearchPhrase", 1, 10) s, COUNT(*) c FROM hits WHERE "SearchPhrase" <> '' GROUP BY s ORDER BY c DESC LIMIT 10 is 1.2x slower than main (1.3x with 2 partitions), although substr itself is faster. The time goes into repartitioning and re-aggregating dictionary group keys, the same cost main already pays when grouping on "SearchPhrase" directly (#24822).

Compacting the dictionary when the aggregate slices its output (#26696) fixes the 2-partition case, but the default stays at 1.2x because the repartition coalescer still rebuilds dictionaries. The next step is to dig into that further, and until then we might need to hold this PR.

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 dug into this further. Part of the cost was hashing and dictionary merging doing work for every dictionary value instead of every row. I'll fix that in the shared code rather than in the aggregate (see #26696), and with a prototype of that this query goes from 1.20x to 1.12x of main. The rest is the general cost of dictionary group keys (#24822), which lower/btrim on dictionary columns already pay on main, so I don't think it needs to block this PR. The guard on the fast path is still needed for Q22.

}

if let ColumnarValue::Array(array) = &args.args[0]
&& matches!(array.data_type(), DataType::Dictionary(_, _))

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.

This fast path runs substr over every dictionary value, so its cost follows the dictionary size rather than the batch size: filter keeps the full dictionary, and Parquet shares one dictionary across batches. That's what the bot's Q22 regression is. On data where c_phone is Dictionary(Int32, Utf8), Q22 goes from 11.8 ms on main to 15.1 ms here (38.6 → 55.3 ms with one partition).

Only taking the fast path when the dictionary isn't larger than the batch brings it back to 12.1 ms (40.4 ms), with the same results:

         if let ColumnarValue::Array(array) = &args.args[0]
             && matches!(array.data_type(), DataType::Dictionary(_, _))
+            && array.as_any_dictionary().values().len() <= array.len()
             && args.args[1..]

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

Labels

functions Changes to functions implementation sqllogictest SQL Logic Tests (.slt)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

substr on Dictionary(_, Utf8) inserts implicit CAST to Utf8View

4 participants