Skip to content

Allow a non-distinct count alongside the single distinct aggregate in SingleDistinctToGroupBy - #24859

Open
adriangb wants to merge 9 commits into
mainfrom
claude/single-distinct-to-groupby-allow-count
Open

Allow a non-distinct count alongside the single distinct aggregate in SingleDistinctToGroupBy#24859
adriangb wants to merge 9 commits into
mainfrom
claude/single-distinct-to-groupby-allow-count

Conversation

@adriangb

@adriangbadriangb commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

No existing issue. We found this while investigating an out-of-memory. Happy to file one if you want a changelog entry.

Rationale for this change

The query

SELECT g, count(*), count(DISTINCT x) FROM t GROUP BY g;

A grouped count(DISTINCT <string>) next to a plain count(*). It is a very common shape, and on main it uses several times more memory than it needs to.

To reproduce, in datafusion-cli. This writes 4,000,000 rows in 500,000 groups, with 2,000,000 distinct (g, x) pairs:

COPY (
SELECT
value % 500000AS g,
'id-'|| CAST(value % 2000000ASVARCHAR) AS x
FROM generate_series(1, 4000000)
) TO 'repro.parquet' STORED AS PARQUET;
CREATE EXTERNAL TABLE t STORED AS PARQUET LOCATION 'repro.parquet';
EXPLAIN FORMAT INDENT SELECT g, count(*), count(DISTINCT x) FROM t GROUP BY g;

The plan today

Projection: t.g, count(Int64(1)) AS count(*), count(DISTINCT t.x)
Aggregate: groupBy=[[t.g]], aggr=[[count(Int64(1)), count(DISTINCT t.x)]]
TableScan: t projection=[g, x]

A single Aggregate that computes the distinct count directly. count(DISTINCT x) has a specialized GroupsAccumulator for the integer types and for no others, so over a string it falls back to GroupsAccumulatorAdapter, which holds one boxed Accumulator — and therefore one hash table — for every group. At 500,000 groups that is 500,000 hash tables.

There is a second effect once there is more than one partition. This plan's partial aggregate sees every group in every partition, so those per-group accumulators are duplicated target_partitions times.

What already exists

DataFusion has a rule for exactly this, SingleDistinctToGroupBy. It rewrites AGG(DISTINCT x) into a two phase group by: an inner aggregate that groups by (group keys, x), so the distinct-ing is done by the hash table that group by already builds, and an outer aggregate over its output. One hash table instead of one per group, and it hash-partitions, so distinct values are split across partitions rather than replicated.

The rule accepts a non-distinct sum, min or max alongside the distinct aggregate. It rejects a non-distinct count. So a single count(*) is enough to keep the slow plan.

The change

Allow that count.

count is the one supported companion whose outer phase must be a different function. The inner group by counts the rows of each (group, distinct value) partition; the outer phase adds those partial counts with sum, because a count over a group is the sum of the counts of any partition of that group.

The plan after

Projection: t.g, CASE WHEN sum(alias2) IS NOT NULL THEN sum(alias2) ELSE Int64(0) END AS count(*), count(alias1) AS count(DISTINCT t.x)
Aggregate: groupBy=[[t.g]], aggr=[[sum(alias2), count(alias1)]]
Aggregate: groupBy=[[t.g, t.x AS alias1]], aggr=[[count(Int64(1)) AS alias2]]
TableScan: t projection=[g, x]

Results are identical. The CASE is the one place the two phases disagree: over an empty input the inner group by emits no rows, and sum of no rows is NULL where count is 0. Restoring the 0 also preserves count's non-nullability.

When this is allowed

The rewrite is not free. Every other aggregate moves down into the inner group by, which holds a row per (group, distinct value) pair rather than per group, and keeps its state at that finer grain. What pays for that is taking the distinct aggregate off the adapter — so if the distinct aggregate was never on the adapter, there is nothing to buy and only the inner group by to pay for.

So the new count is gated on the distinct aggregate reporting that it has no specialized GroupsAccumulator for its argument types. ClickBench q22 is the real case: its count(DISTINCT "UserID") is over an Int64, which has one, so q22 keeps its current plan.

What changes are included in this PR?

Five files. The rule, the gate it needs, and tests.

The rule

datafusion/optimizer/src/single_distinct_to_groupby.rs accepts a non-distinct count as a companion, and gives it sum as its outer phase.

count and sum are resolved from the session function registry, as replace_distinct_aggregate already does for first_value. The rewrite fires only for that exact count, compared by identity rather than by name, so a session with its own count, or with no registry, keeps the previous behaviour.

FILTER and ORDER BY still block the rewrite.

The gate

An optimizer rule has no AccumulatorArgs to call AggregateUDFImpl::groups_accumulator_supported with, and datafusion-optimizer cannot depend on datafusion-functions-aggregate to read count's type list. datafusion/optimizer/Cargo.toml names the intended way out:

If you want to add special handling for a specific function, use the methods on the ScalarUDFImpl or AggregateUDFImpl traits (or add a new method to those traits).

So this adds one trait method:

fngroups_accumulator_supported_for_types(&self,arg_types:&[DataType],is_distinct:bool,) -> Option<bool>{None}

Count is the only implementor, and Count::groups_accumulator_supported now delegates to it, so there is one list of supported types rather than two that can drift.

The default None means the implementation does not answer this question. It is not a third answer: a caller must not read it as either Some(true) or Some(false). This rule rewrites only on Some(false), the one answer that positively reports a call on the adapter. Every aggregate except count answers None today and so keeps its current behaviour.

The gate covers only the count this PR adds. A plan that already qualifies through a non-distinct sum, min or max is rewritten as before, over any distinct argument type — see Pre-existing regressions.

Tests

  • datafusion/sqllogictest/test_files/single_distinct_to_groupby.slt, new.
  • datafusion/substrait/tests/cases/roundtrip_logical_plan.rs: aggregate_distinct_with_having builds its session without this rule, so it keeps round tripping the plan shape it was written for; a companion test covers the rewritten plan's schema and results through Substrait. (The rule's aliases have no Substrait representation, so a rewritten plan does not round trip to an identical plan.)

No existing snapshot in the repository changes.

What is the testing strategy for this PR?

single_distinct_to_groupby.slt asserts every result twice — once under datafusion.optimizer.max_passes = 0 and once under the default — with identical expected blocks. A null-handling or type error therefore shows up as a result mismatch, not only as a plan difference.

The table carries the same values in a VARCHAR column and an INT column, which are the two sides of the gate, and the file asserts both: the VARCHAR distinct rewrites with a count beside it, the INT distinct does not, and the INT distinct still rewrites when it qualifies through sum.

It also asserts that an aggregate answering None stays unrewritten (sum(DISTINCT v) and min(DISTINCT v) beside a count(*) keep the plan they have on main).

Remaining coverage: count(*) vs count(1) vs count(col), grouped and ungrouped; a group whose distinct column is entirely NULL; NULLs in both the distinct and the summed column; empty input in three shapes; HAVING with ORDER BY on the rewritten count; and the same aggregates over a join.

The rule's unit tests cover both sides of the gate directly, and the None answer twice — once with sum(DISTINCT b), once with a test aggregate that leaves the new method at its default.

Run locally on the rebased head, all passing: datafusion-optimizer, datafusion-expr and datafusion-functions-aggregate lib and integration tests, the substrait roundtrip suite, and the whole sqllogictest suite at 511 of 511 files.

Benchmarks

Macro: ClickBench

clickbench_extended q14 is the one query in any suite with this shape — a lone COUNT(DISTINCT <string>) next to a non-distinct COUNT(*), grouped by a high cardinality string. It landed on main in #25026. Three runs of this branch against merge base 16ace4f, DATAFUSION_RUNTIME_MEMORY_LIMIT: 16G (trigger):

runq14 fastestq14 median of 5q14 peak pool
12471 ms → 546 ms, 4.53x4.05x4.4 GiB → 1.3 GiB, -70.5%
22534 ms → 575 ms, 4.40x4.15x4.4 GiB → 1.3 GiB, -69.7%
33909 ms → 1165 ms, 3.36x3.39x4.4 GiB → 1.3 GiB, -70.3%

100,000,000 rows of real ClickBench data. The memory figure reproduces to a tenth of a percent across the three runs.

The limit is 16G because the base side peaks at 4.4 GiB; at 4G the baseline OOMs and there is nothing to compare.

Run 3 was on a contended machine — its suite total is 42.1 s against 30.8 s and 31.0 s — and it reports q0 at 1.30x slower, q7 at 1.14x and q8 at 1.13x. None of those three can change plan here (q0 has three distinct arguments; q7 and q8 have no distinct aggregate at all), so they are an in-experiment control that says what that run's noise floor was.

The standard suite, and q22

q22 is the only query in the standard suite that reaches the new gate, and the gate excludes it. A clickbench_partitioned run (trigger, DATAFUSION_RUNTIME_MEMORY_LIMIT: 4G):

base 16ace4fthis branch
q22 wall clock975.75 ms976.75 msno change
q22 peak pool2.5 MiB3.0 MiB+22.0%
suite total25985 ms26073 ms+0.3%
queries changed0 of 43

The +22.0% is measurement noise on a 2.5 MiB reservation, not an effect of this PR. Three things say so:

  1. The plan is unchanged.EXPLAIN on q22's exact shape against this branch produces a single Aggregate — the rule does not fire, because COUNT(DISTINCT "UserID") is over an Int64, which has a specialized GroupsAccumulator:
    Sort: c DESC NULLS FIRST, fetch=10
    Projection: hits.SearchPhrase, min(hits.URL), min(hits.Title), count(Int64(1)) AS count(*) AS c, count(DISTINCT hits.UserID)
    Aggregate: groupBy=[[hits.SearchPhrase]], aggr=[[min(hits.URL), min(hits.Title), count(Int64(1)), count(DISTINCT hits.UserID)]]
    Filter: ...
    TableScan: hits projection=[SearchPhrase, URL, Title, UserID]
    
  2. The wall clock is flat at 0.1%.
  3. The run's memory noise floor is wider than the reading. 9 of the 43 queries move by more than 5% in both directions, including q23 at -26.1% and q7 at -19.7% — neither of which can change plan under this rule.

No other query changes plan, and that set is empty by construction, not by measurement. The rule needs exactly one distinct argument and accepts only sum, min, max and now count as companions:

querywhy it cannot move
extended q0, q1, q2three, three and four distinct arguments, so fields_set.len() != 1
q4, q5, q8, q10, q11, q13a lone distinct aggregate with no non-distinct companion, so main already rewrites them
q9carries an AVG, which the rule has never accepted
q22the only standard query that reaches the new gate, and its COUNT(DISTINCT "UserID") is over an Int64, which has a specialized GroupsAccumulator

Micro: the reproduction above, swept

Base is e1ca94fb11, 12 commits behind the 16ace4f merge base used for the ClickBench runs above; both are from the same day, and none of the 12 changes aggregate runtime behaviour (the only one touching count_distinct is #25020, which is test-gating only). Both sides are release builds of the same harness: a GreedyMemoryPool of 64 GiB (never reached) wrapped in the in-tree PeakRecordingPool; one parquet file; the result streamed rather than collected; the pool high-water mark and getrusage peak RSS reported. Every figure is the median of three runs. At target_partitions = 1 the three runs agreed to the byte in every cell; at 8, to within 1%, except two branch cells at 7% and 19%.

Every shape is 4,000,000 rows, query SELECT g, count(*), count(DISTINCT x) FROM t GROUP BY g. Each file carries the distinct argument three times — as Utf8, Utf8View and BIGINT — written with an explicit arrow_cast so the type is the file's and not a reader setting.

target_partitions = 8

groupsdistinct valuesargumentpeak poolpeak RSSwall clock
500,0002,000,000Utf82.16x better2.56x better999 ms → 48 ms
500,0002,000,000Utf8View2.68x better2.76x better1036 ms → 40 ms
500,0001,000,000Utf82.83x better3.40x better1020 ms → 41 ms
500,0001,000,000Utf8View3.15x better3.43x better966 ms → 34 ms
102,000,000Utf81.34x better1.85x better80 ms → 41 ms
102,000,000Utf8View1.87x better2.24x better73 ms → 33 ms
12,000,000Utf81.55x better2.14x better202 ms → 45 ms
12,000,000Utf8View1.88x better2.51x better158 ms → 34 ms
2,0004,000,000Utf81.30x worse2.03x better180 ms → 43 ms
2,0004,000,000Utf8View1.10x worse2.55x better185 ms → 38 ms
500,0002,000,000BIGINT1.00x, plan unchanged1.01x43 ms → 43 ms

target_partitions = 1

groupsdistinct valuesargumentpeak poolpeak RSSwall clock
500,0002,000,000Utf82.18x better1.77x better712 ms → 193 ms
500,0002,000,000Utf8View2.70x better2.05x better684 ms → 178 ms
500,0001,000,000Utf83.31x better1.93x better626 ms → 160 ms
500,0001,000,000Utf8View4.42x better2.34x better599 ms → 138 ms
500,0004,000,000Utf81.61x better1.70x better825 ms → 242 ms
500,0004,000,000Utf8View1.96x better2.17x better755 ms → 232 ms
2,0002,000,000Utf81.01x better1.41x better402 ms → 161 ms
2,0002,000,000Utf8View1.26x better1.49x better358 ms → 139 ms
2,0004,000,000Utf81.01x better1.17x better429 ms → 203 ms
2,0004,000,000Utf8View1.26x better1.36x better407 ms → 176 ms

Three things worth calling out:

  • Wall clock was not the point of this PR and is the largest effect. At 8 partitions and 500,000 groups the query goes from about a second to about 40 ms. That is the accumulator-per-group construction cost, which grows with partitions on the unrewritten side while the rewritten side parallelizes.
  • Peak RSS improves in every measured shape, including the two where the pool peak regresses. The unrewritten plan's RSS sits far above what it reserves; millions of small independent hash tables fragment in a way one large table does not.
  • The BIGINT row is the control for the gate. The plan is identical on both sides, and so is every measurement. This is the q22 case, measured directly.

Where the rewrite stops paying

The crossover is in the density of distinct values, not the group count. It arrives only when nearly every row holds a distinct value, and the loss is bounded: 1.30x more peak pool for Utf8 and 1.10x for Utf8View, at 2,000 groups over 4,000,000 fully distinct values at 8 partitions, and a wash at one partition. Those same two cells use about half the RSS and run four times faster.

At 500,000 groups over 4,000,000 fully distinct values — the same density — the rewrite is 1.61x to 1.96x better again, because the unrewritten side now also pays for 500,000 accumulators.

The gate is a proxy

The gate asks which accumulator the distinct aggregate gets. That is not the true discriminator.

What decides the outcome is the cost per distinct value on each side. The rewrite materializes one hash table row per distinct (group keys, x) pair, plus one accumulator slot per companion aggregate at that grain. It wins when the unrewritten accumulator costs more than that per value, and loses when it costs less.

Proxy and discriminator agree for count(DISTINCT x), which is the only case this PR opens. They disagree elsewhere.

Pre-existing regressions, not introduced here

Re-measured on the same base, over 4,000,000 rows in 2,000 groups with 2,000,000 distinct values, comparing the rewritten plan against the same query with a count(*) added to hold the rule off:

  • sum(DISTINCT int_col) and avg(DISTINCT int_col) have no distinct groups accumulator, and main rewrites both today. Both regress: 2.67x more peak pool at one partition, 1.79x at eight.
  • min(DISTINCT x) is far worse. It is the same value as min(x), and min_max correctly ignores is_distinct, so the unrewritten plan holds one scalar per group while the rewrite builds a hash table over every distinct pair: 543x more peak pool at one partition, 126x at eight.

That regression predates this PR and this PR does not extend it — the gate keeps every one of those functions out of the path added here. Reported upstream separately. A cost model is out of scope.

Are there any user-facing changes?

No public API break and no change to query results. AggregateUDFImpl gains one method with a default, which is not breaking for implementors.

Plans for SELECT ..., count(...), count(DISTINCT x) ... GROUP BY ... change shape when x has no specialized GroupsAccumulator. EXPLAIN output for that shape therefore differs, and such queries should use less memory and run faster.

@github-actionsgithub-actionsBot added optimizer Optimizer rules sqllogictest SQL Logic Tests (.slt) substrait Changes to the substrait crate labels Sep 1, 2026
@codecov-commenter

codecov-commenter commented Sep 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.48753% with 56 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.98%. Comparing base (16ace4f) to head (173ae06).
⚠️ Report is 11 commits behind head on main.

Files with missing linesPatch %Lines
...fusion/optimizer/src/single_distinct_to_groupby.rs84.95%18 Missing and 30 partials ⚠️
datafusion/expr/src/udaf.rs65.21%8 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #24859 +/- ##
==========================================
- Coverage 81.72% 80.98% -0.74% 
==========================================
Files 1127 1128 +1 Lines 416273 427663 +11390 Branches 416273 427663 +11390 ==========================================
+ Hits 340188 346335 +6147 - Misses 56094 61351 +5257 + Partials 19991 19977 -14 

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

@adriangb

Copy link
Copy Markdown
ContributorAuthor

run benchmark clickbench_partitioned
env:
DATAFUSION_RUNTIME_MEMORY_LIMIT: 4G

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance:c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c5500225568-2072-x9l4r 6.12.94+ #1 SMP Fri Jul 17 09:42:57 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 claude/single-distinct-to-groupby-allow-count (f47c045) to da89c7c (merge-base) diff

Run configuration
run benchmark clickbench_partitionedenv:
DATAFUSION_RUNTIME_MEMORY_LIMIT: "4G"

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 claude/single-distinct-to-groupby-allow-count (f47c045) to da89c7c (merge-base) diff

Run configuration
run benchmark clickbench_partitionedenv:
DATAFUSION_RUNTIME_MEMORY_LIMIT: "4G"
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 claude_single-distinct-to-groupby-allow-count
--------------------
Benchmark clickbench_partitioned.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query ┃ HEAD ┃ claude_single-distinct-to-groupby-allow-count ┃ Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 0 │ 1.38 ms │ 1.22 ms │ +1.12x faster │
│ QQuery 1 │ 12.34 ms │ 11.86 ms │ no change │
│ QQuery 2 │ 37.35 ms │ 36.73 ms │ no change │
│ QQuery 3 │ 33.25 ms │ 31.14 ms │ +1.07x faster │
│ QQuery 4 │ 265.05 ms │ 221.44 ms │ +1.20x faster │
│ QQuery 5 │ 276.49 ms │ 269.41 ms │ no change │
│ QQuery 6 │ 1.28 ms │ 1.28 ms │ no change │
│ QQuery 7 │ 13.28 ms │ 13.09 ms │ no change │
│ QQuery 8 │ 337.55 ms │ 330.68 ms │ no change │
│ QQuery 9 │ 452.80 ms │ 447.81 ms │ no change │
│ QQuery 10 │ 69.67 ms │ 69.38 ms │ no change │
│ QQuery 11 │ 80.87 ms │ 80.40 ms │ no change │
│ QQuery 12 │ 265.72 ms │ 266.16 ms │ no change │
│ QQuery 13 │ 978.95 ms │ 959.23 ms │ no change │
│ QQuery 14 │ 281.40 ms │ 287.72 ms │ no change │
│ QQuery 15 │ 266.83 ms │ 260.92 ms │ no change │
│ QQuery 16 │ 1228.08 ms │ 1196.31 ms │ no change │
│ QQuery 17 │ 916.26 ms │ 890.67 ms │ no change │
│ QQuery 18 │ 2497.04 ms │ 2464.68 ms │ no change │
│ QQuery 19 │ 28.04 ms │ 30.09 ms │ 1.07x slower │
│ QQuery 20 │ 528.22 ms │ 524.86 ms │ no change │
│ QQuery 21 │ 516.39 ms │ 512.66 ms │ no change │
│ QQuery 22 │ 980.18 ms │ 974.68 ms │ no change │
│ QQuery 23 │ 3061.15 ms │ 3010.47 ms │ no change │
│ QQuery 24 │ 42.00 ms │ 41.44 ms │ no change │
│ QQuery 25 │ 110.73 ms │ 109.66 ms │ no change │
│ QQuery 26 │ 42.25 ms │ 41.12 ms │ no change │
│ QQuery 27 │ 511.60 ms │ 509.48 ms │ no change │
│ QQuery 28 │ 2913.69 ms │ 2885.57 ms │ no change │
│ QQuery 29 │ 41.01 ms │ 41.29 ms │ no change │
│ QQuery 30 │ 298.78 ms │ 296.46 ms │ no change │
│ QQuery 31 │ 273.81 ms │ 284.70 ms │ no change │
│ QQuery 32 │ 3254.62 ms │ 3309.85 ms │ no change │
│ QQuery 33 │ 2515.41 ms │ 2534.95 ms │ no change │
│ QQuery 34 │ 2659.40 ms │ 2562.43 ms │ no change │
│ QQuery 35 │ 280.03 ms │ 278.34 ms │ no change │
│ QQuery 36 │ 65.80 ms │ 65.81 ms │ no change │
│ QQuery 37 │ 35.17 ms │ 35.47 ms │ no change │
│ QQuery 38 │ 39.97 ms │ 40.54 ms │ no change │
│ QQuery 39 │ 133.36 ms │ 130.60 ms │ no change │
│ QQuery 40 │ 13.78 ms │ 13.90 ms │ no change │
│ QQuery 41 │ 13.60 ms │ 13.63 ms │ no change │
│ QQuery 42 │ 12.82 ms │ 13.08 ms │ no change │
└───────────┴────────────┴───────────────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary ┃ ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD) │ 26387.39ms │
│ Total Time (claude_single-distinct-to-groupby-allow-count) │ 26101.22ms │
│ Average Time (HEAD) │ 613.66ms │
│ Average Time (claude_single-distinct-to-groupby-allow-count) │ 607.01ms │
│ Queries Faster │ 3 │
│ Queries Slower │ 1 │
│ Queries with No Change │ 39 │
│ Queries with Failure │ 0 │
└──────────────────────────────────────────────────────────────┴────────────┘
Distribution per query (min / mean ±stddev / max):
Comparing HEAD and claude_single-distinct-to-groupby-allow-count
--------------------
Benchmark clickbench_partitioned.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query ┃ HEAD ┃ claude_single-distinct-to-groupby-allow-count ┃ Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 0 │ 1.38 / 4.40 ±5.86 / 16.12 ms │ 1.22 / 3.94 ±5.36 / 14.66 ms │ +1.12x faster │
│ QQuery 1 │ 12.34 / 12.80 ±0.27 / 13.14 ms │ 11.86 / 12.01 ±0.12 / 12.20 ms │ +1.07x faster │
│ QQuery 2 │ 37.35 / 37.99 ±0.55 / 38.93 ms │ 36.73 / 36.90 ±0.12 / 37.05 ms │ no change │
│ QQuery 3 │ 33.25 / 34.03 ±0.82 / 35.47 ms │ 31.14 / 31.35 ±0.14 / 31.54 ms │ +1.09x faster │
│ QQuery 4 │ 265.05 / 269.97 ±4.66 / 276.23 ms │ 221.44 / 231.96 ±8.16 / 245.31 ms │ +1.16x faster │
│ QQuery 5 │ 276.49 / 288.95 ±13.08 / 309.95 ms │ 269.41 / 274.36 ±4.16 / 280.38 ms │ +1.05x faster │
│ QQuery 6 │ 1.28 / 1.41 ±0.20 / 1.81 ms │ 1.28 / 1.44 ±0.24 / 1.91 ms │ no change │
│ QQuery 7 │ 13.28 / 13.35 ±0.06 / 13.47 ms │ 13.09 / 13.20 ±0.07 / 13.29 ms │ no change │
│ QQuery 8 │ 337.55 / 351.39 ±10.86 / 361.65 ms │ 330.68 / 414.89 ±159.42 / 733.70 ms │ 1.18x slower │
│ QQuery 9 │ 452.80 / 476.14 ±19.03 / 503.53 ms │ 447.81 / 454.59 ±6.52 / 466.88 ms │ no change │
│ QQuery 10 │ 69.67 / 75.80 ±7.42 / 87.62 ms │ 69.38 / 72.74 ±4.97 / 82.59 ms │ no change │
│ QQuery 11 │ 80.87 / 81.53 ±0.43 / 82.17 ms │ 80.40 / 81.73 ±2.17 / 86.04 ms │ no change │
│ QQuery 12 │ 265.72 / 273.17 ±6.19 / 281.39 ms │ 266.16 / 269.76 ±3.69 / 275.76 ms │ no change │
│ QQuery 13 │ 978.95 / 984.79 ±4.26 / 992.10 ms │ 959.23 / 972.59 ±11.09 / 989.55 ms │ no change │
│ QQuery 14 │ 281.40 / 289.20 ±5.44 / 297.13 ms │ 287.72 / 311.18 ±15.57 / 333.71 ms │ 1.08x slower │
│ QQuery 15 │ 266.83 / 271.93 ±2.95 / 275.50 ms │ 260.92 / 274.45 ±10.73 / 292.43 ms │ no change │
│ QQuery 16 │ 1228.08 / 1270.69 ±27.81 / 1314.41 ms │ 1196.31 / 1231.12 ±19.58 / 1251.73 ms │ no change │
│ QQuery 17 │ 916.26 / 944.27 ±23.43 / 979.60 ms │ 890.67 / 931.33 ±22.36 / 957.69 ms │ no change │
│ QQuery 18 │ 2497.04 / 2549.71 ±59.07 / 2659.37 ms │ 2464.68 / 2619.87 ±114.28 / 2736.65 ms │ no change │
│ QQuery 19 │ 28.04 / 29.82 ±2.46 / 34.68 ms │ 30.09 / 30.96 ±0.56 / 31.67 ms │ no change │
│ QQuery 20 │ 528.22 / 535.30 ±4.58 / 540.87 ms │ 524.86 / 548.63 ±18.95 / 574.17 ms │ no change │
│ QQuery 21 │ 516.39 / 520.17 ±3.16 / 525.39 ms │ 512.66 / 517.24 ±4.36 / 525.02 ms │ no change │
│ QQuery 22 │ 980.18 / 986.55 ±4.83 / 992.72 ms │ 974.68 / 988.63 ±9.98 / 1003.70 ms │ no change │
│ QQuery 23 │ 3061.15 / 3176.07 ±88.50 / 3302.98 ms │ 3010.47 / 3045.11 ±30.25 / 3090.12 ms │ no change │
│ QQuery 24 │ 42.00 / 42.34 ±0.44 / 43.20 ms │ 41.44 / 43.22 ±3.08 / 49.38 ms │ no change │
│ QQuery 25 │ 110.73 / 116.59 ±7.38 / 130.21 ms │ 109.66 / 113.03 ±3.80 / 120.17 ms │ no change │
│ QQuery 26 │ 42.25 / 43.27 ±0.79 / 44.67 ms │ 41.12 / 41.64 ±0.66 / 42.93 ms │ no change │
│ QQuery 27 │ 511.60 / 515.42 ±3.73 / 522.37 ms │ 509.48 / 514.21 ±4.78 / 521.97 ms │ no change │
│ QQuery 28 │ 2913.69 / 2942.96 ±24.29 / 2976.11 ms │ 2885.57 / 2943.07 ±47.48 / 3016.49 ms │ no change │
│ QQuery 29 │ 41.01 / 52.70 ±10.75 / 69.66 ms │ 41.29 / 46.47 ±8.04 / 62.43 ms │ +1.13x faster │
│ QQuery 30 │ 298.78 / 305.77 ±4.66 / 313.00 ms │ 296.46 / 317.23 ±30.89 / 378.34 ms │ no change │
│ QQuery 31 │ 273.81 / 287.28 ±7.37 / 294.32 ms │ 284.70 / 291.53 ±4.80 / 296.70 ms │ no change │
│ QQuery 32 │ 3254.62 / 3304.71 ±50.88 / 3397.00 ms │ 3309.85 / 3514.92 ±160.53 / 3712.98 ms │ 1.06x slower │
│ QQuery 33 │ 2515.41 / 2697.29 ±162.83 / 2974.36 ms │ 2534.95 / 2607.56 ±78.80 / 2758.34 ms │ no change │
│ QQuery 34 │ 2659.40 / 2764.19 ±119.47 / 2950.04 ms │ 2562.43 / 2636.57 ±48.78 / 2684.60 ms │ no change │
│ QQuery 35 │ 280.03 / 288.16 ±6.99 / 297.41 ms │ 278.34 / 289.88 ±10.71 / 308.26 ms │ no change │
│ QQuery 36 │ 65.80 / 72.10 ±3.65 / 75.62 ms │ 65.81 / 69.11 ±2.82 / 72.45 ms │ no change │
│ QQuery 37 │ 35.17 / 35.91 ±0.46 / 36.61 ms │ 35.47 / 44.91 ±17.07 / 79.01 ms │ 1.25x slower │
│ QQuery 38 │ 39.97 / 54.20 ±24.14 / 102.34 ms │ 40.54 / 43.23 ±1.55 / 45.17 ms │ +1.25x faster │
│ QQuery 39 │ 133.36 / 140.02 ±4.66 / 147.49 ms │ 130.60 / 134.20 ±3.25 / 140.32 ms │ no change │
│ QQuery 40 │ 13.78 / 14.17 ±0.25 / 14.52 ms │ 13.90 / 14.46 ±0.38 / 15.06 ms │ no change │
│ QQuery 41 │ 13.60 / 13.82 ±0.18 / 14.12 ms │ 13.63 / 14.37 ±0.88 / 16.03 ms │ no change │
│ QQuery 42 │ 12.82 / 18.13 ±9.98 / 38.07 ms │ 13.08 / 14.22 ±1.63 / 17.42 ms │ +1.27x faster │
└───────────┴────────────────────────────────────────┴───────────────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary ┃ ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD) │ 27188.49ms │
│ Total Time (claude_single-distinct-to-groupby-allow-count) │ 27063.85ms │
│ Average Time (HEAD) │ 632.29ms │
│ Average Time (claude_single-distinct-to-groupby-allow-count) │ 629.39ms │
│ Queries Faster │ 8 │
│ Queries Slower │ 4 │
│ Queries with No Change │ 31 │
│ Queries with Failure │ 0 │
└──────────────────────────────────────────────────────────────┴────────────┘

Memory Pool Peaks

Peak MemoryPool reservation per query — what DataFusion's accounting believes it reserved. Recorded only when the benchmark runs with DATAFUSION_RUNTIME_MEMORY_LIMIT set.

Base: da89c7c (merge-base) | Changed: claude/single-distinct-to-groupby-allow-count

clickbench_partitioned

QueryBaseChangedChange
Query 00 B0 B0.0%
Query 1104 B104 B+0.0%
Query 2936 B936 B+0.0%
Query 3312 B312 B+0.0%
Query 4757.7 MiB741.3 MiB-2.2%
Query 51.2 GiB1.2 GiB-0.4%
Query 60 B0 B0.0%
Query 740.1 MiB60.2 MiB+50.1%
Query 8873.1 MiB852.4 MiB-2.4%
Query 9551.7 MiB551.7 MiB+0.0%
Query 10107.6 MiB111.0 MiB+3.2%
Query 11113.2 MiB115.9 MiB+2.4%
Query 121.3 GiB1.3 GiB-0.2%
Query 13998.5 MiB1.1 GiB+8.5%
Query 141.3 GiB1.3 GiB-1.3%
Query 151.1 GiB1.2 GiB+1.9%
Query 161.9 GiB1.7 GiB-10.3%
Query 172.0 GiB1.9 GiB-1.2%
Query 182.0 GiB2.0 GiB-2.9%
Query 190 B0 B0.0%
Query 20104 B104 B+0.0%
Query 213.3 MiB3.3 MiB-0.0%
Query 223.1 MiB7.3 MiB+132.2%
Query 2326.8 MiB25.7 MiB-4.0%
Query 2458.8 MiB58.6 MiB-0.4%
Query 25170.3 MiB173.2 MiB+1.7%
Query 2659.9 MiB60.3 MiB+0.6%
Query 272.2 MiB2.2 MiB+0.0%
Query 281.5 GiB1.4 GiB-3.7%
Query 29624 B624 B+0.0%
Query 30698.5 MiB707.1 MiB+1.2%
Query 311.5 GiB1.5 GiB+3.2%
Query 32926.9 MiB966.9 MiB+4.3%
Query 332.0 GiB2.1 GiB+5.2%
Query 342.1 GiB2.1 GiB-2.6%
Query 35612.0 MiB622.3 MiB+1.7%
Query 36113.6 MiB111.1 MiB-2.2%
Query 376.9 MiB6.9 MiB+0.0%
Query 385.2 MiB5.2 MiB-0.4%
Query 39297.5 MiB297.8 MiB+0.1%
Query 402.0 MiB1.8 MiB-8.3%
Query 413.1 MiB3.1 MiB+0.0%
Query 421.6 MiB1.6 MiB+5.1%

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.

BenchmarkSideMax pool peakPeak RSSGapRSS / pool
clickbench_partitionedbase (da89c7c (merge-base))2.1 GiB8.7 GiB6.6 GiB4.1×
clickbench_partitionedchanged (claude/single-distinct-to-groupby-allow-count)2.1 GiB8.6 GiB6.4 GiB4.0×
Resource Usage

clickbench_partitioned — base (merge-base)

MetricValue
Wall time140.0s
Peak memory8.7 GiB
Avg memory5.3 GiB
CPU user1382.5s
CPU sys131.6s
Peak spill0 B

clickbench_partitioned — branch

MetricValue
Wall time140.0s
Peak memory8.6 GiB
Avg memory5.1 GiB
CPU user1376.6s
CPU sys132.7s
Peak spill0 B

File an issue against this benchmark runner

adriangb added a commit to pydantic/datafusion that referenced this pull request Sep 1, 2026
The two grouped `COUNT(DISTINCT <string>)` memory limit tests only reach
the per group accumulators while
`single_distinct_aggregation_to_group_by` declines to rewrite the query.
They leant on `count(*)` for that, which the rule rejects only because
`count` is missing from the `sum`/`min`/`max` allow list.
apache#24859 proposes adding `count` to that list, which would
rewrite the query, remove the accumulators, and leave both tests passing
at any memory limit while still looking like they test something.
Aggregate `avg(payload)` over a new `Int64` column instead. `avg` cannot
be added to that list: the rule re-aggregates its own partial results
over the deduplicated inner group by, and averaging per group averages of
different sizes gives the wrong answer. That is why ClickBench Q9 keeps
its distinct aggregate under apache#24859.
Verified from the physical plan with apache#24859 cherry-picked on top of this
branch: the `avg` query still plans as
`aggr=[count(DISTINCT t.value), avg(t.payload)]`, while the `count(*)`
query becomes `aggr=[count(alias1), sum(alias2)]` over an inner
`GROUP BY group_key, value`, and drops from needing ~1.9 MB to ~0.9 MB.
Re-swept both thresholds against the base commit. `Utf8` needs ~35.5 MB
before and ~1.9 MB after; `Utf8View` needs ~123 MB before and ~2.5 MB
after, so the 8 MB and 16 MB limits keep at least 4x margin on each side
and are unchanged.
@adriangb

Copy link
Copy Markdown
ContributorAuthor

run benchmark clickbench_partitioned clickbench_partitioned clickbench_partitioned

env:
DATAFUSION_RUNTIME_MEMORY_LIMIT: 4G

@github-actionsgithub-actionsBot added logical-expr Logical plan and expressions functions Changes to functions implementation labels Sep 2, 2026
@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance:c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c5514755290-2103-9pm4r 6.12.94+ #1 SMP Fri Jul 17 09:42:57 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 claude/single-distinct-to-groupby-allow-count (d510dd4) to da89c7c (merge-base) diff

Run configuration
run benchmark clickbench_partitionedenv:
DATAFUSION_RUNTIME_MEMORY_LIMIT: "4G"

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-c5514755290-2104-xv7bw 6.12.94+ #1 SMP Fri Jul 17 09:42:57 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 claude/single-distinct-to-groupby-allow-count (d510dd4) to da89c7c (merge-base) diff

Run configuration
run benchmark clickbench_partitionedenv:
DATAFUSION_RUNTIME_MEMORY_LIMIT: "4G"

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-c5514755290-2105-kkpnq 6.12.94+ #1 SMP Fri Jul 17 09:42:57 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 claude/single-distinct-to-groupby-allow-count (d510dd4) to da89c7c (merge-base) diff

Run configuration
run benchmark clickbench_partitionedenv:
DATAFUSION_RUNTIME_MEMORY_LIMIT: "4G"

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 claude/single-distinct-to-groupby-allow-count (d510dd4) to da89c7c (merge-base) diff

Run configuration
run benchmark clickbench_partitionedenv:
DATAFUSION_RUNTIME_MEMORY_LIMIT: "4G"
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 claude_single-distinct-to-groupby-allow-count
--------------------
Benchmark clickbench_partitioned.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query ┃ HEAD ┃ claude_single-distinct-to-groupby-allow-count ┃ Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 0 │ 1.22 ms │ 1.21 ms │ no change │
│ QQuery 1 │ 11.84 ms │ 11.85 ms │ no change │
│ QQuery 2 │ 37.10 ms │ 36.43 ms │ no change │
│ QQuery 3 │ 31.32 ms │ 31.30 ms │ no change │
│ QQuery 4 │ 227.95 ms │ 223.17 ms │ no change │
│ QQuery 5 │ 278.44 ms │ 273.19 ms │ no change │
│ QQuery 6 │ 1.28 ms │ 1.27 ms │ no change │
│ QQuery 7 │ 13.60 ms │ 13.41 ms │ no change │
│ QQuery 8 │ 334.97 ms │ 336.15 ms │ no change │
│ QQuery 9 │ 462.84 ms │ 455.82 ms │ no change │
│ QQuery 10 │ 70.72 ms │ 70.27 ms │ no change │
│ QQuery 11 │ 82.51 ms │ 81.12 ms │ no change │
│ QQuery 12 │ 271.21 ms │ 270.32 ms │ no change │
│ QQuery 13 │ 990.69 ms │ 971.51 ms │ no change │
│ QQuery 14 │ 285.44 ms │ 284.86 ms │ no change │
│ QQuery 15 │ 271.87 ms │ 263.20 ms │ no change │
│ QQuery 16 │ 1224.39 ms │ 1207.65 ms │ no change │
│ QQuery 17 │ 938.17 ms │ 928.40 ms │ no change │
│ QQuery 18 │ 2525.15 ms │ 2495.36 ms │ no change │
│ QQuery 19 │ 28.34 ms │ 27.90 ms │ no change │
│ QQuery 20 │ 525.20 ms │ 523.29 ms │ no change │
│ QQuery 21 │ 517.57 ms │ 515.53 ms │ no change │
│ QQuery 22 │ 988.34 ms │ 983.42 ms │ no change │
│ QQuery 23 │ 3019.56 ms │ 3020.27 ms │ no change │
│ QQuery 24 │ 41.26 ms │ 41.15 ms │ no change │
│ QQuery 25 │ 109.96 ms │ 110.13 ms │ no change │
│ QQuery 26 │ 42.24 ms │ 41.22 ms │ no change │
│ QQuery 27 │ 512.82 ms │ 522.42 ms │ no change │
│ QQuery 28 │ 2944.09 ms │ 2916.45 ms │ no change │
│ QQuery 29 │ 41.01 ms │ 41.20 ms │ no change │
│ QQuery 30 │ 303.22 ms │ 300.73 ms │ no change │
│ QQuery 31 │ 289.32 ms │ 282.18 ms │ no change │
│ QQuery 32 │ 3284.67 ms │ 3283.17 ms │ no change │
│ QQuery 33 │ 2556.04 ms │ 2592.71 ms │ no change │
│ QQuery 34 │ 2630.88 ms │ 2647.50 ms │ no change │
│ QQuery 35 │ 338.93 ms │ 284.82 ms │ +1.19x faster │
│ QQuery 36 │ 69.03 ms │ 64.59 ms │ +1.07x faster │
│ QQuery 37 │ 36.77 ms │ 35.69 ms │ no change │
│ QQuery 38 │ 42.58 ms │ 40.14 ms │ +1.06x faster │
│ QQuery 39 │ 152.46 ms │ 133.46 ms │ +1.14x faster │
│ QQuery 40 │ 16.37 ms │ 14.15 ms │ +1.16x faster │
│ QQuery 41 │ 15.40 ms │ 13.77 ms │ +1.12x faster │
│ QQuery 42 │ 13.40 ms │ 13.30 ms │ no change │
└───────────┴────────────┴───────────────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary ┃ ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD) │ 26580.17ms │
│ Total Time (claude_single-distinct-to-groupby-allow-count) │ 26405.69ms │
│ Average Time (HEAD) │ 618.14ms │
│ Average Time (claude_single-distinct-to-groupby-allow-count) │ 614.09ms │
│ Queries Faster │ 6 │
│ Queries Slower │ 0 │
│ Queries with No Change │ 37 │
│ Queries with Failure │ 0 │
└──────────────────────────────────────────────────────────────┴────────────┘
Distribution per query (min / mean ±stddev / max):
Comparing HEAD and claude_single-distinct-to-groupby-allow-count
--------------------
Benchmark clickbench_partitioned.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query ┃ HEAD ┃ claude_single-distinct-to-groupby-allow-count ┃ Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 0 │ 1.22 / 4.03 ±5.52 / 15.07 ms │ 1.21 / 4.07 ±5.62 / 15.31 ms │ no change │
│ QQuery 1 │ 11.84 / 12.04 ±0.13 / 12.20 ms │ 11.85 / 12.04 ±0.14 / 12.18 ms │ no change │
│ QQuery 2 │ 37.10 / 37.32 ±0.21 / 37.70 ms │ 36.43 / 36.77 ±0.27 / 37.14 ms │ no change │
│ QQuery 3 │ 31.32 / 31.96 ±0.75 / 33.37 ms │ 31.30 / 32.04 ±0.72 / 33.31 ms │ no change │
│ QQuery 4 │ 227.95 / 230.00 ±2.10 / 233.78 ms │ 223.17 / 225.87 ±1.68 / 227.65 ms │ no change │
│ QQuery 5 │ 278.44 / 279.03 ±0.47 / 279.59 ms │ 273.19 / 277.34 ±4.14 / 282.77 ms │ no change │
│ QQuery 6 │ 1.28 / 1.42 ±0.22 / 1.86 ms │ 1.27 / 1.42 ±0.23 / 1.88 ms │ no change │
│ QQuery 7 │ 13.60 / 13.73 ±0.09 / 13.86 ms │ 13.41 / 14.00 ±0.38 / 14.52 ms │ no change │
│ QQuery 8 │ 334.97 / 339.76 ±4.12 / 345.24 ms │ 336.15 / 422.42 ±164.81 / 751.89 ms │ 1.24x slower │
│ QQuery 9 │ 462.84 / 473.37 ±5.98 / 479.47 ms │ 455.82 / 466.55 ±9.97 / 482.30 ms │ no change │
│ QQuery 10 │ 70.72 / 72.05 ±1.05 / 73.56 ms │ 70.27 / 74.76 ±8.10 / 90.96 ms │ no change │
│ QQuery 11 │ 82.51 / 85.86 ±6.16 / 98.18 ms │ 81.12 / 82.61 ±1.13 / 83.95 ms │ no change │
│ QQuery 12 │ 271.21 / 274.38 ±3.28 / 279.68 ms │ 270.32 / 274.99 ±3.16 / 280.26 ms │ no change │
│ QQuery 13 │ 990.69 / 997.89 ±5.99 / 1005.99 ms │ 971.51 / 994.19 ±14.38 / 1009.62 ms │ no change │
│ QQuery 14 │ 285.44 / 291.48 ±3.21 / 294.20 ms │ 284.86 / 288.27 ±3.12 / 292.14 ms │ no change │
│ QQuery 15 │ 271.87 / 274.95 ±3.34 / 281.34 ms │ 263.20 / 272.39 ±8.06 / 287.09 ms │ no change │
│ QQuery 16 │ 1224.39 / 1253.92 ±19.39 / 1281.87 ms │ 1207.65 / 1240.15 ±23.07 / 1267.82 ms │ no change │
│ QQuery 17 │ 938.17 / 964.06 ±24.48 / 1003.91 ms │ 928.40 / 937.91 ±7.10 / 949.12 ms │ no change │
│ QQuery 18 │ 2525.15 / 2581.99 ±30.34 / 2607.28 ms │ 2495.36 / 2605.08 ±169.51 / 2941.99 ms │ no change │
│ QQuery 19 │ 28.34 / 28.90 ±0.58 / 29.94 ms │ 27.90 / 28.46 ±0.48 / 29.07 ms │ no change │
│ QQuery 20 │ 525.20 / 536.89 ±7.11 / 544.22 ms │ 523.29 / 528.63 ±7.76 / 543.93 ms │ no change │
│ QQuery 21 │ 517.57 / 520.31 ±1.95 / 522.69 ms │ 515.53 / 523.04 ±6.77 / 532.36 ms │ no change │
│ QQuery 22 │ 988.34 / 1001.63 ±12.13 / 1017.49 ms │ 983.42 / 994.83 ±12.13 / 1016.41 ms │ no change │
│ QQuery 23 │ 3019.56 / 3035.98 ±16.50 / 3065.28 ms │ 3020.27 / 3049.11 ±17.13 / 3071.25 ms │ no change │
│ QQuery 24 │ 41.26 / 42.45 ±1.80 / 46.03 ms │ 41.15 / 42.19 ±1.24 / 44.58 ms │ no change │
│ QQuery 25 │ 109.96 / 112.39 ±3.13 / 118.54 ms │ 110.13 / 111.02 ±0.57 / 111.82 ms │ no change │
│ QQuery 26 │ 42.24 / 42.97 ±0.69 / 43.93 ms │ 41.22 / 41.58 ±0.27 / 42.02 ms │ no change │
│ QQuery 27 │ 512.82 / 526.02 ±11.16 / 539.89 ms │ 522.42 / 528.21 ±4.27 / 533.77 ms │ no change │
│ QQuery 28 │ 2944.09 / 2971.20 ±20.57 / 3005.46 ms │ 2916.45 / 2947.55 ±27.99 / 2989.42 ms │ no change │
│ QQuery 29 │ 41.01 / 43.94 ±5.16 / 54.25 ms │ 41.20 / 44.30 ±4.62 / 53.34 ms │ no change │
│ QQuery 30 │ 303.22 / 310.81 ±9.03 / 326.79 ms │ 300.73 / 309.74 ±7.10 / 321.89 ms │ no change │
│ QQuery 31 │ 289.32 / 298.20 ±6.15 / 308.15 ms │ 282.18 / 291.67 ±11.42 / 308.58 ms │ no change │
│ QQuery 32 │ 3284.67 / 3322.65 ±27.89 / 3350.85 ms │ 3283.17 / 3347.84 ±43.18 / 3408.73 ms │ no change │
│ QQuery 33 │ 2556.04 / 2627.15 ±45.76 / 2678.81 ms │ 2592.71 / 2646.43 ±49.60 / 2715.34 ms │ no change │
│ QQuery 34 │ 2630.88 / 2838.41 ±161.17 / 3093.67 ms │ 2647.50 / 2735.70 ±59.70 / 2816.97 ms │ no change │
│ QQuery 35 │ 338.93 / 354.55 ±13.22 / 373.40 ms │ 284.82 / 296.84 ±9.47 / 307.76 ms │ +1.19x faster │
│ QQuery 36 │ 69.03 / 76.47 ±6.95 / 89.31 ms │ 64.59 / 71.83 ±5.20 / 77.62 ms │ +1.06x faster │
│ QQuery 37 │ 36.77 / 37.43 ±0.54 / 38.26 ms │ 35.69 / 37.36 ±1.53 / 39.68 ms │ no change │
│ QQuery 38 │ 42.58 / 54.55 ±21.43 / 97.34 ms │ 40.14 / 40.46 ±0.28 / 40.99 ms │ +1.35x faster │
│ QQuery 39 │ 152.46 / 156.61 ±3.70 / 162.37 ms │ 133.46 / 154.18 ±24.35 / 202.03 ms │ no change │
│ QQuery 40 │ 16.37 / 17.84 ±1.91 / 21.58 ms │ 14.15 / 14.62 ±0.32 / 15.05 ms │ +1.22x faster │
│ QQuery 41 │ 15.40 / 15.89 ±0.50 / 16.58 ms │ 13.77 / 14.16 ±0.31 / 14.61 ms │ +1.12x faster │
│ QQuery 42 │ 13.40 / 13.91 ±0.30 / 14.23 ms │ 13.30 / 13.43 ±0.10 / 13.54 ms │ no change │
└───────────┴────────────────────────────────────────┴───────────────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary ┃ ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD) │ 27206.37ms │
│ Total Time (claude_single-distinct-to-groupby-allow-count) │ 27076.07ms │
│ Average Time (HEAD) │ 632.71ms │
│ Average Time (claude_single-distinct-to-groupby-allow-count) │ 629.68ms │
│ Queries Faster │ 5 │
│ Queries Slower │ 1 │
│ Queries with No Change │ 37 │
│ Queries with Failure │ 0 │
└──────────────────────────────────────────────────────────────┴────────────┘

Memory Pool Peaks

Peak MemoryPool reservation per query — what DataFusion's accounting believes it reserved. Recorded only when the benchmark runs with DATAFUSION_RUNTIME_MEMORY_LIMIT set.

Base: da89c7c (merge-base) | Changed: claude/single-distinct-to-groupby-allow-count

clickbench_partitioned

QueryBaseChangedChange
Query 00 B0 B0.0%
Query 1104 B104 B+0.0%
Query 2936 B936 B+0.0%
Query 3312 B312 B+0.0%
Query 4761.5 MiB758.3 MiB-0.4%
Query 51.2 GiB1.1 GiB-4.1%
Query 60 B0 B0.0%
Query 750.2 MiB70.1 MiB+39.7%
Query 8869.5 MiB883.8 MiB+1.6%
Query 9593.9 MiB551.7 MiB-7.1%
Query 10107.5 MiB114.9 MiB+6.8%
Query 11111.9 MiB117.8 MiB+5.3%
Query 121.3 GiB1.3 GiB+1.2%
Query 131018.6 MiB1014.4 MiB-0.4%
Query 141.3 GiB1.3 GiB-2.1%
Query 151.2 GiB1.2 GiB-0.2%
Query 161.7 GiB2.0 GiB+14.8%
Query 171.8 GiB1.9 GiB+6.3%
Query 181.8 GiB1.7 GiB-4.6%
Query 190 B0 B0.0%
Query 20104 B104 B+0.0%
Query 213.3 MiB3.3 MiB-0.6%
Query 224.2 MiB3.1 MiB-24.4%
Query 2327.3 MiB28.4 MiB+4.1%
Query 2459.3 MiB60.8 MiB+2.5%
Query 25178.2 MiB177.9 MiB-0.2%
Query 2661.5 MiB60.2 MiB-2.2%
Query 272.4 MiB2.2 MiB-9.1%
Query 281.5 GiB1.5 GiB+1.5%
Query 29624 B624 B+0.0%
Query 30724.4 MiB733.8 MiB+1.3%
Query 311.5 GiB1.5 GiB-3.1%
Query 32928.7 MiB927.5 MiB-0.1%
Query 332.1 GiB2.1 GiB-2.0%
Query 342.0 GiB2.2 GiB+6.4%
Query 35596.8 MiB593.9 MiB-0.5%
Query 36124.8 MiB117.7 MiB-5.7%
Query 376.3 MiB6.9 MiB+10.0%
Query 385.6 MiB5.7 MiB+0.7%
Query 39298.3 MiB297.8 MiB-0.2%
Query 401.8 MiB1.7 MiB-5.9%
Query 413.1 MiB3.1 MiB+0.0%
Query 421.9 MiB1.6 MiB-11.1%

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.

BenchmarkSideMax pool peakPeak RSSGapRSS / pool
clickbench_partitionedbase (da89c7c (merge-base))2.1 GiB8.5 GiB6.4 GiB4.0×
clickbench_partitionedchanged (claude/single-distinct-to-groupby-allow-count)2.2 GiB8.7 GiB6.5 GiB4.0×
Resource Usage

clickbench_partitioned — base (merge-base)

MetricValue
Wall time140.0s
Peak memory8.5 GiB
Avg memory4.9 GiB
CPU user1369.2s
CPU sys136.3s
Peak spill0 B

clickbench_partitioned — branch

MetricValue
Wall time140.0s
Peak memory8.7 GiB
Avg memory5.1 GiB
CPU user1369.4s
CPU sys135.5s
Peak spill0 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 claude/single-distinct-to-groupby-allow-count (d510dd4) to da89c7c (merge-base) diff

Run configuration
run benchmark clickbench_partitionedenv:
DATAFUSION_RUNTIME_MEMORY_LIMIT: "4G"
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 claude_single-distinct-to-groupby-allow-count
--------------------
Benchmark clickbench_partitioned.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query ┃ HEAD ┃ claude_single-distinct-to-groupby-allow-count ┃ Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 0 │ 1.27 ms │ 1.22 ms │ no change │
│ QQuery 1 │ 12.22 ms │ 11.62 ms │ no change │
│ QQuery 2 │ 38.04 ms │ 36.38 ms │ no change │
│ QQuery 3 │ 32.62 ms │ 30.96 ms │ +1.05x faster │
│ QQuery 4 │ 227.33 ms │ 220.88 ms │ no change │
│ QQuery 5 │ 274.86 ms │ 270.90 ms │ no change │
│ QQuery 6 │ 1.28 ms │ 1.25 ms │ no change │
│ QQuery 7 │ 13.55 ms │ 13.08 ms │ no change │
│ QQuery 8 │ 332.26 ms │ 327.53 ms │ no change │
│ QQuery 9 │ 446.62 ms │ 457.33 ms │ no change │
│ QQuery 10 │ 69.37 ms │ 69.51 ms │ no change │
│ QQuery 11 │ 82.09 ms │ 80.21 ms │ no change │
│ QQuery 12 │ 269.35 ms │ 266.19 ms │ no change │
│ QQuery 13 │ 966.91 ms │ 966.01 ms │ no change │
│ QQuery 14 │ 284.98 ms │ 319.46 ms │ 1.12x slower │
│ QQuery 15 │ 276.84 ms │ 312.31 ms │ 1.13x slower │
│ QQuery 16 │ 1238.76 ms │ 1256.69 ms │ no change │
│ QQuery 17 │ 898.65 ms │ 940.45 ms │ no change │
│ QQuery 18 │ 2455.27 ms │ 2464.09 ms │ no change │
│ QQuery 19 │ 29.77 ms │ 27.66 ms │ +1.08x faster │
│ QQuery 20 │ 517.52 ms │ 510.92 ms │ no change │
│ QQuery 21 │ 518.26 ms │ 509.53 ms │ no change │
│ QQuery 22 │ 977.48 ms │ 977.16 ms │ no change │
│ QQuery 23 │ 3000.63 ms │ 2958.54 ms │ no change │
│ QQuery 24 │ 40.79 ms │ 41.28 ms │ no change │
│ QQuery 25 │ 109.83 ms │ 108.64 ms │ no change │
│ QQuery 26 │ 41.31 ms │ 41.13 ms │ no change │
│ QQuery 27 │ 508.08 ms │ 508.58 ms │ no change │
│ QQuery 28 │ 2907.16 ms │ 2980.89 ms │ no change │
│ QQuery 29 │ 41.41 ms │ 41.08 ms │ no change │
│ QQuery 30 │ 302.75 ms │ 297.41 ms │ no change │
│ QQuery 31 │ 273.33 ms │ 280.18 ms │ no change │
│ QQuery 32 │ 3282.50 ms │ 3248.08 ms │ no change │
│ QQuery 33 │ 2590.55 ms │ 2520.60 ms │ no change │
│ QQuery 34 │ 2692.59 ms │ 2580.73 ms │ no change │
│ QQuery 35 │ 277.90 ms │ 300.20 ms │ 1.08x slower │
│ QQuery 36 │ 67.22 ms │ 70.81 ms │ 1.05x slower │
│ QQuery 37 │ 35.86 ms │ 38.17 ms │ 1.06x slower │
│ QQuery 38 │ 40.22 ms │ 42.64 ms │ 1.06x slower │
│ QQuery 39 │ 130.00 ms │ 159.50 ms │ 1.23x slower │
│ QQuery 40 │ 13.96 ms │ 16.06 ms │ 1.15x slower │
│ QQuery 41 │ 13.59 ms │ 15.23 ms │ 1.12x slower │
│ QQuery 42 │ 13.39 ms │ 14.62 ms │ 1.09x slower │
└───────────┴────────────┴───────────────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary ┃ ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD) │ 26348.35ms │
│ Total Time (claude_single-distinct-to-groupby-allow-count) │ 26335.75ms │
│ Average Time (HEAD) │ 612.75ms │
│ Average Time (claude_single-distinct-to-groupby-allow-count) │ 612.46ms │
│ Queries Faster │ 2 │
│ Queries Slower │ 10 │
│ Queries with No Change │ 31 │
│ Queries with Failure │ 0 │
└──────────────────────────────────────────────────────────────┴────────────┘
Distribution per query (min / mean ±stddev / max):
Comparing HEAD and claude_single-distinct-to-groupby-allow-count
--------------------
Benchmark clickbench_partitioned.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query ┃ HEAD ┃ claude_single-distinct-to-groupby-allow-count ┃ Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 0 │ 1.27 / 4.22 ±5.73 / 15.68 ms │ 1.22 / 3.95 ±5.39 / 14.73 ms │ +1.07x faster │
│ QQuery 1 │ 12.22 / 12.58 ±0.25 / 12.87 ms │ 11.62 / 12.05 ±0.27 / 12.45 ms │ no change │
│ QQuery 2 │ 38.04 / 38.46 ±0.31 / 38.92 ms │ 36.38 / 36.55 ±0.24 / 37.01 ms │ no change │
│ QQuery 3 │ 32.62 / 33.85 ±1.02 / 35.31 ms │ 30.96 / 31.38 ±0.61 / 32.58 ms │ +1.08x faster │
│ QQuery 4 │ 227.33 / 229.65 ±1.63 / 232.07 ms │ 220.88 / 225.64 ±3.37 / 231.19 ms │ no change │
│ QQuery 5 │ 274.86 / 277.89 ±1.70 / 279.65 ms │ 270.90 / 274.70 ±3.88 / 281.59 ms │ no change │
│ QQuery 6 │ 1.28 / 1.46 ±0.21 / 1.85 ms │ 1.25 / 1.39 ±0.22 / 1.82 ms │ no change │
│ QQuery 7 │ 13.55 / 13.67 ±0.10 / 13.85 ms │ 13.08 / 13.21 ±0.09 / 13.36 ms │ no change │
│ QQuery 8 │ 332.26 / 340.41 ±6.04 / 347.47 ms │ 327.53 / 335.83 ±5.09 / 341.12 ms │ no change │
│ QQuery 9 │ 446.62 / 459.03 ±7.80 / 466.14 ms │ 457.33 / 467.33 ±11.39 / 488.93 ms │ no change │
│ QQuery 10 │ 69.37 / 70.42 ±0.88 / 72.00 ms │ 69.51 / 70.92 ±1.98 / 74.83 ms │ no change │
│ QQuery 11 │ 82.09 / 82.50 ±0.30 / 82.94 ms │ 80.21 / 81.06 ±0.87 / 82.12 ms │ no change │
│ QQuery 12 │ 269.35 / 271.44 ±2.11 / 275.24 ms │ 266.19 / 272.74 ±3.97 / 278.22 ms │ no change │
│ QQuery 13 │ 966.91 / 975.94 ±6.10 / 982.32 ms │ 966.01 / 986.04 ±21.18 / 1026.61 ms │ no change │
│ QQuery 14 │ 284.98 / 314.15 ±16.60 / 330.66 ms │ 319.46 / 328.91 ±8.76 / 344.68 ms │ no change │
│ QQuery 15 │ 276.84 / 298.01 ±18.55 / 320.53 ms │ 312.31 / 317.90 ±5.32 / 327.14 ms │ 1.07x slower │
│ QQuery 16 │ 1238.76 / 1273.35 ±33.77 / 1337.52 ms │ 1256.69 / 1342.81 ±52.12 / 1416.22 ms │ 1.05x slower │
│ QQuery 17 │ 898.65 / 921.54 ±16.68 / 941.53 ms │ 940.45 / 998.66 ±39.90 / 1060.94 ms │ 1.08x slower │
│ QQuery 18 │ 2455.27 / 2599.91 ±116.87 / 2750.73 ms │ 2464.09 / 2541.95 ±69.68 / 2672.30 ms │ no change │
│ QQuery 19 │ 29.77 / 30.61 ±0.77 / 31.58 ms │ 27.66 / 28.22 ±0.55 / 29.20 ms │ +1.08x faster │
│ QQuery 20 │ 517.52 / 523.78 ±4.51 / 531.10 ms │ 510.92 / 524.84 ±8.84 / 534.47 ms │ no change │
│ QQuery 21 │ 518.26 / 530.80 ±8.21 / 540.00 ms │ 509.53 / 525.11 ±11.95 / 545.07 ms │ no change │
│ QQuery 22 │ 977.48 / 987.68 ±8.84 / 1002.03 ms │ 977.16 / 987.63 ±10.29 / 1006.44 ms │ no change │
│ QQuery 23 │ 3000.63 / 3046.06 ±37.37 / 3113.11 ms │ 2958.54 / 3039.22 ±88.25 / 3204.36 ms │ no change │
│ QQuery 24 │ 40.79 / 44.59 ±5.79 / 56.13 ms │ 41.28 / 48.60 ±13.44 / 75.48 ms │ 1.09x slower │
│ QQuery 25 │ 109.83 / 114.20 ±6.64 / 127.43 ms │ 108.64 / 109.31 ±0.56 / 110.33 ms │ no change │
│ QQuery 26 │ 41.31 / 46.52 ±7.65 / 61.73 ms │ 41.13 / 47.43 ±9.70 / 66.74 ms │ no change │
│ QQuery 27 │ 508.08 / 516.37 ±5.30 / 523.45 ms │ 508.58 / 513.40 ±3.44 / 518.91 ms │ no change │
│ QQuery 28 │ 2907.16 / 2942.96 ±35.07 / 3008.63 ms │ 2980.89 / 3037.02 ±49.41 / 3099.25 ms │ no change │
│ QQuery 29 │ 41.41 / 52.36 ±20.82 / 93.99 ms │ 41.08 / 57.44 ±31.57 / 120.56 ms │ 1.10x slower │
│ QQuery 30 │ 302.75 / 307.19 ±4.85 / 315.84 ms │ 297.41 / 310.09 ±8.62 / 322.16 ms │ no change │
│ QQuery 31 │ 273.33 / 283.38 ±6.78 / 293.89 ms │ 280.18 / 290.74 ±9.38 / 303.48 ms │ no change │
│ QQuery 32 │ 3282.50 / 3445.59 ±145.25 / 3620.75 ms │ 3248.08 / 3336.39 ±61.54 / 3412.53 ms │ no change │
│ QQuery 33 │ 2590.55 / 2688.67 ±62.72 / 2769.88 ms │ 2520.60 / 2616.21 ±99.98 / 2766.88 ms │ no change │
│ QQuery 34 │ 2692.59 / 2721.40 ±37.26 / 2794.25 ms │ 2580.73 / 2649.07 ±72.19 / 2784.12 ms │ no change │
│ QQuery 35 │ 277.90 / 282.94 ±5.46 / 292.69 ms │ 300.20 / 333.61 ±20.64 / 354.46 ms │ 1.18x slower │
│ QQuery 36 │ 67.22 / 84.13 ±27.76 / 139.47 ms │ 70.81 / 73.49 ±2.53 / 77.55 ms │ +1.14x faster │
│ QQuery 37 │ 35.86 / 37.09 ±1.37 / 39.54 ms │ 38.17 / 38.51 ±0.27 / 38.87 ms │ no change │
│ QQuery 38 │ 40.22 / 41.81 ±1.50 / 44.11 ms │ 42.64 / 57.36 ±24.04 / 105.23 ms │ 1.37x slower │
│ QQuery 39 │ 130.00 / 140.95 ±10.12 / 152.99 ms │ 159.50 / 165.39 ±5.39 / 173.60 ms │ 1.17x slower │
│ QQuery 40 │ 13.96 / 14.43 ±0.31 / 14.82 ms │ 16.06 / 17.49 ±1.13 / 19.06 ms │ 1.21x slower │
│ QQuery 41 │ 13.59 / 15.05 ±1.86 / 18.67 ms │ 15.23 / 15.47 ±0.17 / 15.75 ms │ no change │
│ QQuery 42 │ 13.39 / 13.56 ±0.24 / 14.03 ms │ 14.62 / 14.81 ±0.14 / 15.04 ms │ 1.09x slower │
└───────────┴────────────────────────────────────────┴───────────────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary ┃ ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD) │ 27130.58ms │
│ Total Time (claude_single-distinct-to-groupby-allow-count) │ 27179.87ms │
│ Average Time (HEAD) │ 630.94ms │
│ Average Time (claude_single-distinct-to-groupby-allow-count) │ 632.09ms │
│ Queries Faster │ 4 │
│ Queries Slower │ 10 │
│ Queries with No Change │ 29 │
│ Queries with Failure │ 0 │
└──────────────────────────────────────────────────────────────┴────────────┘

Memory Pool Peaks

Peak MemoryPool reservation per query — what DataFusion's accounting believes it reserved. Recorded only when the benchmark runs with DATAFUSION_RUNTIME_MEMORY_LIMIT set.

Base: da89c7c (merge-base) | Changed: claude/single-distinct-to-groupby-allow-count

clickbench_partitioned

QueryBaseChangedChange
Query 00 B0 B0.0%
Query 1104 B104 B+0.0%
Query 2936 B936 B+0.0%
Query 3312 B312 B+0.0%
Query 4757.5 MiB772.8 MiB+2.0%
Query 51.1 GiB1.2 GiB+6.7%
Query 60 B0 B0.0%
Query 760.2 MiB50.3 MiB-16.4%
Query 8846.7 MiB899.0 MiB+6.2%
Query 9677.7 MiB593.5 MiB-12.4%
Query 10104.3 MiB112.8 MiB+8.1%
Query 11110.0 MiB114.7 MiB+4.2%
Query 121.3 GiB1.3 GiB-3.8%
Query 131.0 GiB1.0 GiB+0.5%
Query 141.2 GiB1.3 GiB+1.8%
Query 151.2 GiB1.2 GiB+0.9%
Query 161.8 GiB1.8 GiB-1.0%
Query 171.7 GiB2.1 GiB+26.7%
Query 181.9 GiB1.8 GiB-8.0%
Query 190 B0 B0.0%
Query 20104 B104 B+0.0%
Query 213.3 MiB3.3 MiB-1.2%
Query 223.0 MiB2.6 MiB-13.8%
Query 2329.3 MiB25.2 MiB-13.9%
Query 2460.8 MiB61.6 MiB+1.2%
Query 25172.1 MiB177.3 MiB+3.0%
Query 2663.8 MiB60.8 MiB-4.7%
Query 272.4 MiB2.4 MiB+0.0%
Query 281.5 GiB1.5 GiB-4.8%
Query 29624 B624 B+0.0%
Query 30731.0 MiB662.2 MiB-9.4%
Query 311.5 GiB1.5 GiB+0.0%
Query 32928.7 MiB926.4 MiB-0.3%
Query 332.1 GiB2.1 GiB+1.9%
Query 342.1 GiB2.0 GiB-4.4%
Query 35596.1 MiB595.9 MiB-0.0%
Query 36122.5 MiB113.9 MiB-7.0%
Query 376.9 MiB6.9 MiB+0.0%
Query 385.2 MiB5.6 MiB+8.5%
Query 39297.8 MiB297.8 MiB+0.0%
Query 401.7 MiB2.0 MiB+17.2%
Query 413.1 MiB3.1 MiB-0.0%
Query 421.7 MiB2.1 MiB+25.1%

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.

BenchmarkSideMax pool peakPeak RSSGapRSS / pool
clickbench_partitionedbase (da89c7c (merge-base))2.1 GiB9.3 GiB7.2 GiB4.4×
clickbench_partitionedchanged (claude/single-distinct-to-groupby-allow-count)2.1 GiB8.6 GiB6.5 GiB4.1×
Resource Usage

clickbench_partitioned — base (merge-base)

MetricValue
Wall time140.0s
Peak memory9.3 GiB
Avg memory5.6 GiB
CPU user1383.9s
CPU sys128.0s
Peak spill0 B

clickbench_partitioned — branch

MetricValue
Wall time140.0s
Peak memory8.6 GiB
Avg memory5.1 GiB
CPU user1379.7s
CPU sys133.6s
Peak spill0 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 claude/single-distinct-to-groupby-allow-count (d510dd4) to da89c7c (merge-base) diff

Run configuration
run benchmark clickbench_partitionedenv:
DATAFUSION_RUNTIME_MEMORY_LIMIT: "4G"
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 claude_single-distinct-to-groupby-allow-count
--------------------
Benchmark clickbench_partitioned.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Query ┃ HEAD ┃ claude_single-distinct-to-groupby-allow-count ┃ Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ QQuery 0 │ 1.25 ms │ 1.26 ms │ no change │
│ QQuery 1 │ 12.04 ms │ 12.31 ms │ no change │
│ QQuery 2 │ 37.24 ms │ 36.99 ms │ no change │
│ QQuery 3 │ 32.08 ms │ 31.95 ms │ no change │
│ QQuery 4 │ 238.55 ms │ 234.46 ms │ no change │
│ QQuery 5 │ 283.29 ms │ 283.96 ms │ no change │
│ QQuery 6 │ 1.29 ms │ 1.33 ms │ no change │
│ QQuery 7 │ 13.56 ms │ 13.59 ms │ no change │
│ QQuery 8 │ 359.70 ms │ 359.76 ms │ no change │
│ QQuery 9 │ 484.05 ms │ 496.72 ms │ no change │
│ QQuery 10 │ 72.00 ms │ 73.83 ms │ no change │
│ QQuery 11 │ 83.33 ms │ 84.50 ms │ no change │
│ QQuery 12 │ 280.11 ms │ 285.04 ms │ no change │
│ QQuery 13 │ 1018.57 ms │ 1021.09 ms │ no change │
│ QQuery 14 │ 296.70 ms │ 296.85 ms │ no change │
│ QQuery 15 │ 286.15 ms │ 291.21 ms │ no change │
│ QQuery 16 │ 1238.77 ms │ 1238.03 ms │ no change │
│ QQuery 17 │ 958.92 ms │ 999.14 ms │ no change │
│ QQuery 18 │ 2558.25 ms │ 2624.19 ms │ no change │
│ QQuery 19 │ 29.59 ms │ 29.33 ms │ no change │
│ QQuery 20 │ 517.42 ms │ 529.51 ms │ no change │
│ QQuery 21 │ 516.30 ms │ 527.07 ms │ no change │
│ QQuery 22 │ 1002.16 ms │ 1014.28 ms │ no change │
│ QQuery 23 │ 3089.00 ms │ 3127.31 ms │ no change │
│ QQuery 24 │ 42.94 ms │ 41.81 ms │ no change │
│ QQuery 25 │ 113.80 ms │ 113.77 ms │ no change │
│ QQuery 26 │ 43.30 ms │ 42.36 ms │ no change │
│ QQuery 27 │ 518.63 ms │ 524.14 ms │ no change │
│ QQuery 28 │ 2978.05 ms │ 2986.05 ms │ no change │
│ QQuery 29 │ 42.11 ms │ 42.46 ms │ no change │
│ QQuery 30 │ 327.55 ms │ 321.58 ms │ no change │
│ QQuery 31 │ 291.20 ms │ 293.10 ms │ no change │
│ QQuery 32 │ 3488.76 ms │ 3437.55 ms │ no change │
│ QQuery 33 │ 2680.67 ms │ 2632.40 ms │ no change │
│ QQuery 34 │ 2798.53 ms │ 2759.08 ms │ no change │
│ QQuery 35 │ 310.34 ms │ 305.11 ms │ no change │
│ QQuery 36 │ 67.79 ms │ 68.55 ms │ no change │
│ QQuery 37 │ 37.27 ms │ 36.66 ms │ no change │
│ QQuery 38 │ 41.42 ms │ 41.20 ms │ no change │
│ QQuery 39 │ 136.99 ms │ 137.55 ms │ no change │
│ QQuery 40 │ 15.21 ms │ 15.10 ms │ no change │
│ QQuery 41 │ 14.91 ms │ 14.67 ms │ no change │
│ QQuery 42 │ 14.52 ms │ 14.10 ms │ no change │
└───────────┴────────────┴───────────────────────────────────────────────┴───────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary ┃ ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD) │ 27374.33ms │
│ Total Time (claude_single-distinct-to-groupby-allow-count) │ 27440.98ms │
│ Average Time (HEAD) │ 636.61ms │
│ Average Time (claude_single-distinct-to-groupby-allow-count) │ 638.16ms │
│ Queries Faster │ 0 │
│ Queries Slower │ 0 │
│ Queries with No Change │ 43 │
│ Queries with Failure │ 0 │
└──────────────────────────────────────────────────────────────┴────────────┘
Distribution per query (min / mean ±stddev / max):
Comparing HEAD and claude_single-distinct-to-groupby-allow-count
--------------------
Benchmark clickbench_partitioned.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query ┃ HEAD ┃ claude_single-distinct-to-groupby-allow-count ┃ Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 0 │ 1.25 / 4.13 ±5.61 / 15.36 ms │ 1.26 / 4.21 ±5.70 / 15.60 ms │ no change │
│ QQuery 1 │ 12.04 / 12.53 ±0.27 / 12.81 ms │ 12.31 / 12.58 ±0.22 / 12.93 ms │ no change │
│ QQuery 2 │ 37.24 / 37.77 ±0.36 / 38.15 ms │ 36.99 / 37.28 ±0.33 / 37.91 ms │ no change │
│ QQuery 3 │ 32.08 / 33.20 ±0.78 / 34.13 ms │ 31.95 / 32.11 ±0.13 / 32.28 ms │ no change │
│ QQuery 4 │ 238.55 / 241.74 ±2.31 / 245.36 ms │ 234.46 / 241.16 ±4.40 / 247.32 ms │ no change │
│ QQuery 5 │ 283.29 / 287.49 ±3.02 / 291.53 ms │ 283.96 / 290.74 ±4.13 / 296.74 ms │ no change │
│ QQuery 6 │ 1.29 / 1.43 ±0.22 / 1.87 ms │ 1.33 / 1.49 ±0.24 / 1.96 ms │ no change │
│ QQuery 7 │ 13.56 / 14.48 ±1.15 / 16.75 ms │ 13.59 / 15.23 ±2.48 / 20.17 ms │ 1.05x slower │
│ QQuery 8 │ 359.70 / 364.02 ±2.84 / 366.95 ms │ 359.76 / 364.52 ±2.98 / 369.14 ms │ no change │
│ QQuery 9 │ 484.05 / 495.25 ±7.45 / 504.76 ms │ 496.72 / 504.66 ±8.58 / 520.61 ms │ no change │
│ QQuery 10 │ 72.00 / 72.65 ±0.49 / 73.38 ms │ 73.83 / 75.06 ±0.97 / 76.47 ms │ no change │
│ QQuery 11 │ 83.33 / 84.08 ±0.90 / 85.80 ms │ 84.50 / 87.00 ±2.99 / 92.69 ms │ no change │
│ QQuery 12 │ 280.11 / 286.46 ±4.35 / 292.04 ms │ 285.04 / 300.48 ±12.85 / 320.77 ms │ no change │
│ QQuery 13 │ 1018.57 / 1028.17 ±13.05 / 1054.04 ms │ 1021.09 / 1028.06 ±3.86 / 1032.88 ms │ no change │
│ QQuery 14 │ 296.70 / 302.18 ±4.52 / 307.42 ms │ 296.85 / 309.11 ±18.89 / 346.57 ms │ no change │
│ QQuery 15 │ 286.15 / 296.29 ±14.52 / 324.69 ms │ 291.21 / 298.52 ±9.86 / 317.77 ms │ no change │
│ QQuery 16 │ 1238.77 / 1291.13 ±34.65 / 1338.84 ms │ 1238.03 / 1326.62 ±74.17 / 1458.76 ms │ no change │
│ QQuery 17 │ 958.92 / 981.52 ±24.26 / 1026.10 ms │ 999.14 / 1011.04 ±19.32 / 1049.47 ms │ no change │
│ QQuery 18 │ 2558.25 / 2607.84 ±28.90 / 2637.26 ms │ 2624.19 / 2684.03 ±36.03 / 2735.05 ms │ no change │
│ QQuery 19 │ 29.59 / 34.34 ±7.12 / 48.36 ms │ 29.33 / 29.71 ±0.44 / 30.52 ms │ +1.16x faster │
│ QQuery 20 │ 517.42 / 534.52 ±10.93 / 550.84 ms │ 529.51 / 534.55 ±4.05 / 540.57 ms │ no change │
│ QQuery 21 │ 516.30 / 526.22 ±7.42 / 536.04 ms │ 527.07 / 534.47 ±5.75 / 544.70 ms │ no change │
│ QQuery 22 │ 1002.16 / 1004.25 ±2.64 / 1009.41 ms │ 1014.28 / 1022.86 ±6.46 / 1032.03 ms │ no change │
│ QQuery 23 │ 3089.00 / 3168.46 ±48.27 / 3237.70 ms │ 3127.31 / 3152.36 ±15.67 / 3172.62 ms │ no change │
│ QQuery 24 │ 42.94 / 43.79 ±0.86 / 45.43 ms │ 41.81 / 46.31 ±6.05 / 57.74 ms │ 1.06x slower │
│ QQuery 25 │ 113.80 / 115.70 ±1.56 / 118.50 ms │ 113.77 / 115.43 ±1.50 / 117.97 ms │ no change │
│ QQuery 26 │ 43.30 / 47.76 ±4.32 / 54.98 ms │ 42.36 / 44.15 ±1.81 / 47.37 ms │ +1.08x faster │
│ QQuery 27 │ 518.63 / 527.15 ±5.77 / 534.51 ms │ 524.14 / 531.48 ±7.46 / 543.78 ms │ no change │
│ QQuery 28 │ 2978.05 / 3085.15 ±76.47 / 3198.31 ms │ 2986.05 / 3002.24 ±14.17 / 3023.59 ms │ no change │
│ QQuery 29 │ 42.11 / 43.05 ±0.84 / 44.28 ms │ 42.46 / 42.93 ±0.40 / 43.59 ms │ no change │
│ QQuery 30 │ 327.55 / 334.22 ±4.62 / 341.41 ms │ 321.58 / 335.40 ±11.36 / 351.21 ms │ no change │
│ QQuery 31 │ 291.20 / 305.89 ±10.06 / 321.25 ms │ 293.10 / 300.07 ±4.92 / 307.82 ms │ no change │
│ QQuery 32 │ 3488.76 / 3514.56 ±16.08 / 3536.17 ms │ 3437.55 / 3484.28 ±33.12 / 3524.84 ms │ no change │
│ QQuery 33 │ 2680.67 / 2854.69 ±114.70 / 3012.87 ms │ 2632.40 / 2720.12 ±76.93 / 2839.08 ms │ no change │
│ QQuery 34 │ 2798.53 / 2899.67 ±58.26 / 2964.30 ms │ 2759.08 / 2795.77 ±25.02 / 2828.75 ms │ no change │
│ QQuery 35 │ 310.34 / 316.23 ±5.72 / 325.13 ms │ 305.11 / 321.58 ±9.58 / 332.81 ms │ no change │
│ QQuery 36 │ 67.79 / 73.23 ±6.64 / 85.89 ms │ 68.55 / 73.16 ±3.75 / 78.74 ms │ no change │
│ QQuery 37 │ 37.27 / 42.84 ±8.99 / 60.50 ms │ 36.66 / 49.52 ±24.98 / 99.48 ms │ 1.16x slower │
│ QQuery 38 │ 41.42 / 43.62 ±2.67 / 47.65 ms │ 41.20 / 41.59 ±0.23 / 41.84 ms │ no change │
│ QQuery 39 │ 136.99 / 150.38 ±11.56 / 170.92 ms │ 137.55 / 155.87 ±12.92 / 168.88 ms │ no change │
│ QQuery 40 │ 15.21 / 15.81 ±0.61 / 16.88 ms │ 15.10 / 15.52 ±0.22 / 15.68 ms │ no change │
│ QQuery 41 │ 14.91 / 15.22 ±0.29 / 15.62 ms │ 14.67 / 14.86 ±0.17 / 15.18 ms │ no change │
│ QQuery 42 │ 14.52 / 14.57 ±0.05 / 14.63 ms │ 14.10 / 14.42 ±0.19 / 14.65 ms │ no change │
└───────────┴────────────────────────────────────────┴───────────────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary ┃ ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD) │ 28153.72ms │
│ Total Time (claude_single-distinct-to-groupby-allow-count) │ 28002.54ms │
│ Average Time (HEAD) │ 654.74ms │
│ Average Time (claude_single-distinct-to-groupby-allow-count) │ 651.22ms │
│ Queries Faster │ 2 │
│ Queries Slower │ 3 │
│ Queries with No Change │ 38 │
│ Queries with Failure │ 0 │
└──────────────────────────────────────────────────────────────┴────────────┘

Memory Pool Peaks

Peak MemoryPool reservation per query — what DataFusion's accounting believes it reserved. Recorded only when the benchmark runs with DATAFUSION_RUNTIME_MEMORY_LIMIT set.

Base: da89c7c (merge-base) | Changed: claude/single-distinct-to-groupby-allow-count

clickbench_partitioned

QueryBaseChangedChange
Query 00 B0 B0.0%
Query 1104 B104 B+0.0%
Query 2936 B936 B+0.0%
Query 3312 B312 B+0.0%
Query 4777.8 MiB770.4 MiB-1.0%
Query 51.2 GiB1.2 GiB+2.3%
Query 60 B0 B0.0%
Query 740.1 MiB60.1 MiB+49.8%
Query 8849.5 MiB849.1 MiB-0.0%
Query 9593.7 MiB593.9 MiB+0.0%
Query 10114.7 MiB111.8 MiB-2.5%
Query 11107.8 MiB112.1 MiB+4.0%
Query 121.3 GiB1.3 GiB+5.2%
Query 131.0 GiB1.0 GiB-0.7%
Query 141.3 GiB1.3 GiB+1.5%
Query 151.2 GiB1.2 GiB-1.6%
Query 161.8 GiB2.1 GiB+16.7%
Query 171.9 GiB2.0 GiB+6.9%
Query 181.9 GiB1.8 GiB-5.8%
Query 190 B0 B0.0%
Query 20104 B104 B+0.0%
Query 213.3 MiB3.6 MiB+9.3%
Query 224.8 MiB3.0 MiB-37.3%
Query 2326.8 MiB31.9 MiB+19.0%
Query 2459.3 MiB59.1 MiB-0.3%
Query 25176.1 MiB184.0 MiB+4.5%
Query 2664.8 MiB60.9 MiB-5.9%
Query 272.2 MiB2.4 MiB+10.0%
Query 281.5 GiB1.5 GiB-1.2%
Query 29624 B624 B+0.0%
Query 30716.0 MiB697.8 MiB-2.5%
Query 311.5 GiB1.5 GiB+1.6%
Query 32970.2 MiB928.1 MiB-4.3%
Query 332.1 GiB2.1 GiB+1.2%
Query 342.1 GiB2.1 GiB+1.3%
Query 35602.6 MiB608.0 MiB+0.9%
Query 36123.2 MiB118.1 MiB-4.2%
Query 376.3 MiB7.5 MiB+20.0%
Query 385.2 MiB4.7 MiB-10.6%
Query 39298.1 MiB298.1 MiB+0.0%
Query 402.0 MiB2.0 MiB-0.1%
Query 413.1 MiB3.1 MiB+0.0%
Query 421.7 MiB2.1 MiB+24.5%

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.

BenchmarkSideMax pool peakPeak RSSGapRSS / pool
clickbench_partitionedbase (da89c7c (merge-base))2.1 GiB8.4 GiB6.3 GiB4.0×
clickbench_partitionedchanged (claude/single-distinct-to-groupby-allow-count)2.1 GiB8.4 GiB6.3 GiB4.0×
Resource Usage

clickbench_partitioned — base (merge-base)

MetricValue
Wall time145.0s
Peak memory8.4 GiB
Avg memory5.1 GiB
CPU user1430.0s
CPU sys136.8s
Peak spill0 B

clickbench_partitioned — branch

MetricValue
Wall time145.0s
Peak memory8.4 GiB
Avg memory5.1 GiB
CPU user1420.1s
CPU sys139.9s
Peak spill0 B

File an issue against this benchmark runner

adriangb added a commit that referenced this pull request Sep 3, 2026
The two grouped `COUNT(DISTINCT <string>)` memory limit tests only reach
the per group accumulators while
`single_distinct_aggregation_to_group_by` declines to rewrite the query.
They leant on `count(*)` for that, which the rule rejects only because
`count` is missing from the `sum`/`min`/`max` allow list.
#24859 proposes adding `count` to that list, which would
rewrite the query, remove the accumulators, and leave both tests passing
at any memory limit while still looking like they test something.
Aggregate `avg(payload)` over a new `Int64` column instead. `avg` cannot
be added to that list: the rule re-aggregates its own partial results
over the deduplicated inner group by, and averaging per group averages of
different sizes gives the wrong answer. That is why ClickBench Q9 keeps
its distinct aggregate under #24859.
Verified from the physical plan with #24859 cherry-picked on top of this
branch: the `avg` query still plans as
`aggr=[count(DISTINCT t.value), avg(t.payload)]`, while the `count(*)`
query becomes `aggr=[count(alias1), sum(alias2)]` over an inner
`GROUP BY group_key, value`, and drops from needing ~1.9 MB to ~0.9 MB.
Re-swept both thresholds against the base commit. `Utf8` needs ~35.5 MB
before and ~1.9 MB after; `Utf8View` needs ~123 MB before and ~2.5 MB
after, so the 8 MB and 16 MB limits keep at least 4x margin on each side
and are unchanged.
pullBot pushed a commit to TCeason/arrow-datafusion that referenced this pull request Sep 6, 2026
…TINCT) group (apache#24857)
A grouped `COUNT(DISTINCT <string>)` over 4,000 groups holding 2 short
strings each needs a 36 MB memory budget. It needs 2.0 MB after this
change.
Every group gets its own hash table, and each table is allocated at
warm-up size before the group holds anything, so the memory the query
needs tracks the number of groups rather than the amount of data. The
query also reports less memory than it holds, so a memory limit does not
stop it at the right point.
## Reproduction
This needs only `datafusion-cli`. There is no patch, no custom allocator
and no data file.
```sql
-- repro.sql
SET datafusion.execution.target_partitions = 1;
-- 4,000 groups with 2 distinct short strings in each.
-- avg() stops SingleDistinctToGroupBy from rewriting the distinct aggregate away.
SELECT g, count(DISTINCT s) AS d, avg(p) AS a
FROM (
SELECT v % 4000 AS g, 'v' || CAST(v AS VARCHAR) AS s, v AS p
FROM generate_series(0, 7999) AS t(v)
)
GROUP BY g
ORDER BY g
LIMIT 3;
```
```
datafusion-cli -m 8M -f repro.sql
```
`s` is a `Utf8View` column, so this exercises `ArrowBytesViewMap`. The
8,000 rows arrive in one batch, so the aggregate builds all 4,000
accumulators before it can emit or spill.
Current `main` at `20d1c56761` fails:
```
Resources exhausted: Additional allocation failed for SingleHashAggregateStream[0] with top memory
consumers (across reservations) as:
DataFusion-Cli#1(can spill: false) consumed 0.0 B, peak 0.0 B,
SingleHashAggregateStream[0]#2(can spill: true) consumed 0.0 B, peak 48.0 B,
TopK[0]#3(can spill: false) consumed 0.0 B, peak 0.0 B.
Error: Failed to allocate additional 111.1 MB for SingleHashAggregateStream[0] with 0.0 B already
allocated for this reservation - 8.0 MB remain available for the total memory pool:
greedy(used: 0.0 B, pool_size: 8.0 MB)
```
This branch returns the rows:
```
+---+---+--------+
| g | d | a |
+---+---+--------+
| 0 | 2 | 2000.0 |
| 1 | 2 | 2001.0 |
| 2 | 2 | 2002.0 |
+---+---+--------+
3 row(s) fetched.
Elapsed 0.006 seconds.
```
Those 4,000 accumulators hold 8,000 short strings, which is about 100 KB
of data. The base asks the pool for 111.1 MB to hold it. This branch
runs the same query inside `-m 3M`. Both builds return the same rows,
and the base does so at `-m 200M`. Each run takes well under a second,
and the outcome repeats exactly over three runs on each side.
## How much it improves
The minimum memory limit at which that query completes, bisected on each
side:
| value column | before | after |
| --- | --- | --- |
| `Utf8` | fails 34 MB, passes 36 MB | fails 1.8 MB, passes 2.0 MB |
| `Utf8View` | fails 120 MB, passes 124 MB | fails 2.4 MB, passes 2.6 MB
|
`clickbench_extended` at `DATAFUSION_RUNTIME_MEMORY_LIMIT: 4G`, pool
peak over six runs:
| query | base | this branch | change |
| --- | --- | --- | --- |
| Q2, grouped, 4 string distincts | 98.1 to 98.6 MiB | 11.7 MiB in all
six runs | -88.1% |
| Q1, ungrouped string distincts | 3.4 MiB | 2.4 MiB | -29.4% |
| Q0, ungrouped, high cardinality | 796.8 to 834.8 MiB | 846.5 to 885.5
MiB | +6.3% |
Q2 is the only query in any benchmark suite that puts a grouped
`COUNT(DISTINCT)` on a non-integer column.
Q0 costs more, and it is the one disclosed cost of this PR. Those extra
bytes are memory Q0 always held and the pool could not see, not new
allocation; appendix A has the three-build decomposition that separates
the two. Latency does not move anywhere, which is what an
allocation-sizing change should do.
## Which issue does this PR close?
No existing issue. I found this when I investigated a production out of
memory. I can file an issue if you want it in the changelog.
## Rationale for this change
Pre-allocating is right for the one long-lived map behind a `GROUP BY`
on a string column. It is wrong for the distinct-count accumulators,
because `GroupsAccumulatorAdapter` creates one accumulator per group and
most groups hold a handful of values. There the warm-up dwarfs the data.
Both maps also under-report the table they hold. `ArrowBytesViewMap`
left the control bytes out. `ArrowBytesMap` charged the table only when
it grew, so a map that stayed inside its pre-allocation reported its
table as free forever. A memory limit acted on a number that was too
small.
`clear_shrink` is the third part. The aggregate stream calls it to hand
memory back before it spills and before a downstream sort. It restored
the warm-up capacity instead of releasing it, so nothing came back.
## What changes are included in this PR?
- `new` on both maps allocates nothing. A new `with_capacity` keeps the
previous pre-allocating behavior. `GroupValuesBytes` and
`GroupValuesBytesView` use `with_capacity`, and the two distinct-count
accumulators use `new`. A map remembers how it was built, so `take`
warms it back up the way it started.
- `size()` reports `HashTable::allocation_size()`, the real hashbrown
allocation including the control bytes, in place of the old estimate.
- A new `clear_and_release` drops every allocation the map holds, and
`clear_shrink` calls it.
- The value buffer rounds each growth up to a power of two. A lazily
grown buffer and a pre-allocated one then sit on one ladder, so a lazy
map is never the larger of the two for the same values. Growth stays
geometric.
- `benches/arrow_bytes_map.rs` moves to `with_capacity` so it keeps
measuring the pre-allocating constructor.
## What is the testing strategy for this PR?
Two tests in `datafusion/core/tests/memory_limit/mod.rs`,
`group_by_count_distinct_utf8` and `group_by_count_distinct_utf8_view`,
turn the headline claim into a pass or a fail rather than a number. They
run the reproduction query over 4,000 groups with spilling disabled and
`target_partitions` pinned to 1, so completing means the query fits the
budget rather than spills out of it. The limits are 8 MB and 16 MB, at
least 4x clear of both cliffs in the table above. Both tests fail on the
merge base and pass here, over five consecutive runs. The `avg(payload)`
in the query is load bearing; appendix C says why.
Unit tests cover what the memory-limit tests cannot see: that `new`
allocates nothing, that `with_capacity` reports a table size bracketed
by an independently derived lower bound, that `take` preserves the
configured capacity, that `clear_shrink` drops the reported size to near
zero, and that a lazily grown buffer never exceeds a pre-allocated one
holding the same values.
Run locally on the rebased head, all passing:
`datafusion-physical-expr-common` (87 lib, 8 doc),
`datafusion-functions-aggregate-common` (49),
`datafusion-functions-aggregate -- count_distinct` (2),
`datafusion-physical-plan -- group_values` (96) and the `memory_limit`
module (39, which includes the `count_distinct_spill` test that arrived
on `main` in apache#24888 and apache#24918). `cargo fmt --check` and `cargo clippy
--all-targets -D warnings` are clean on the changed crates. CI has not
yet run this branch against the new base.
No query results change.
## Are there any user-facing changes?
Yes, in `datafusion-physical-expr-common`. `ArrowBytesMap::new` and
`ArrowBytesViewMap::new` no longer pre-allocate, and callers that want
the previous behavior should use the new `with_capacity`. Both types
also gain `clear_and_release`. This changes an existing public
constructor rather than adding one, so tell me if you would like the
`api change` label.
For users, a grouped `COUNT(DISTINCT)` on string and binary columns uses
much less memory and reports its usage to the `MemoryPool` accurately. A
query that previously hit a memory limit may now succeed.
---
## Appendix A: Query 0 costs 6.3% more
Q0 is `COUNT(DISTINCT)` over three high-cardinality strings with no
`GROUP BY`. It is a handful of maps that each grow to millions of
entries, which is the opposite population from the one this PR targets.
The pre-allocation was never the dominant cost there, so removing it
buys nothing.
Over six runs the base spans 796.8 to 834.8 MiB and this branch spans
846.5 to 885.5 MiB. The ranges do not overlap, so the effect is real and
not run-to-run noise.
Three local builds on a deterministic subset separate the two changes.
The middle build differs from the base only in the accounting, because
restoring the warm-up makes the constructors byte-identical to base:
| build | Q0 pool peak |
| --- | --- |
| base | 48,421,820 |
| this branch with the warm-up restored | 51,048,396 |
| this branch | 51,018,828 |
That decomposes the increase exactly. +2,626,576 is the accounting
correction: `allocation_size()` charges the real hashbrown allocation,
which is `4 * buckets + 5,384` more than the old formula, being the
control bytes plus the 7/8 load-factor slack. -29,568 is the lazy
constructor, which makes Q0 slightly better.
Reverting the accounting would restore an under-report of about 19% on
this path. That under-report is the bug this PR exists to fix, and the
memory-limit result above depends on fixing it.
## Appendix B: what one accumulator costs
One per-group accumulator holding a single 24-byte value:
| | before, actual | before, reported | after |
| --- | --- | --- | --- |
| `BytesDistinctCountAccumulator` | 14,648 B | 8,240 B | 180 B |
| `BytesViewDistinctCountAccumulator` | 33,920 B | 28,792 B | 260 B |
The middle column is the reporting gap. The `Utf8` map really held
14,648 bytes and reported 8,240, because the whole hash table was
invisible to the old accounting.
These are measured directly rather than asserted in a test, since the
exact numbers follow the hashbrown layout.
## Appendix C: notes on the tests and the benchmarks
The memory-limit query uses `avg(payload)`, not `count(*)`. A
non-distinct `count` lets `SingleDistinctToGroupBy` rewrite the distinct
aggregate into a plain two-stage `GROUP BY`. The per-group accumulators
would then never exist, and the tests would pass by construction on the
base commit too. That rule accepts a non-distinct `sum`, `min` or `max`
because each re-aggregates its own partial results correctly over the
deduplicated inner group by. `avg` does not, so the rule can never admit
it under any extension, including the one apache#24859 proposes.
The benchmark figures were measured against the previous merge base
`da89c7c85b`. They have not been re-run against the current base
`20d1c56761`. The commits after `84f07da` on this branch touch only
`datafusion/core/tests/memory_limit/mod.rs`, so nothing on this branch
since then can move a benchmark, but the base itself has moved.
Pool peak is the instrument here, not peak RSS. Pool peak reproduces to
under 1% on a 98 MiB query and exactly on the 3.4 and 11.7 MiB ones.
Peak RSS on this harness has a 4.1% standard deviation over 11 readings
of identical code plus a 2.3% order bias, and shows no effect from this
change once that null is accounted for.
## Follow-ups, not in this PR
- The same undercount remains at five other production
`insert_accounted` call sites: `group_values/row.rs:171`,
`multi_group_by/mod.rs:434,554`, `multi_group_by/dictionary.rs:197,584`
and `array_agg.rs:989`. Each is one map per query, so the absolute error
is bounded, and the fix is the same one-line swap.
- The `count_distinct_groups` benchmarks in
`datafusion/functions-aggregate/benches/count_distinct.rs` cover
`Int64`, `Int32` and `UInt32` only, so this path has no criterion
coverage.
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@adriangb
adriangbforce-pushed the claude/single-distinct-to-groupby-allow-count branch from d3dbc09 to e0e5a8cCompareSeptember 7, 2026 06:21
@adriangb
adriangb marked this pull request as ready for review September 7, 2026 07:10
@adriangb

Copy link
Copy Markdown
ContributorAuthor

run benchmark clickbench_extended clickbench_extended clickbench_extended

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance:c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c5566611080-2195-sk5cw 6.12.94+ #1 SMP Fri Jul 17 09:42:57 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 claude/single-distinct-to-groupby-allow-count (3d3cf6b) to e1ca94f (merge-base) diff

Run configuration
run benchmark clickbench_extended

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-c5566611080-2196-nd76m 6.12.94+ #1 SMP Fri Jul 17 09:42:57 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 claude/single-distinct-to-groupby-allow-count (3d3cf6b) to e1ca94f (merge-base) diff

Run configuration
run benchmark clickbench_extended

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-c5566611080-2197-5pnzw 6.12.94+ #1 SMP Fri Jul 17 09:42:57 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 claude/single-distinct-to-groupby-allow-count (3d3cf6b) to e1ca94f (merge-base) diff

Run configuration
run benchmark clickbench_extended

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 claude/single-distinct-to-groupby-allow-count (3d3cf6b) to e1ca94f (merge-base) diff

Run configuration
run benchmark clickbench_extended
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 claude_single-distinct-to-groupby-allow-count
--------------------
Benchmark clickbench_extended.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query ┃ HEAD ┃ claude_single-distinct-to-groupby-allow-count ┃ Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 0 │ 780.26 ms │ 783.34 ms │ no change │
│ QQuery 1 │ 188.77 ms │ 188.75 ms │ no change │
│ QQuery 2 │ 449.28 ms │ 447.96 ms │ no change │
│ QQuery 3 │ 314.27 ms │ 313.49 ms │ no change │
│ QQuery 4 │ 1149.65 ms │ 1159.99 ms │ no change │
│ QQuery 5 │ 11158.56 ms │ 10462.77 ms │ +1.07x faster │
│ QQuery 6 │ 2.66 ms │ 2.64 ms │ no change │
│ QQuery 7 │ 689.69 ms │ 690.22 ms │ no change │
│ QQuery 8 │ 407.32 ms │ 415.12 ms │ no change │
│ QQuery 9 │ 2882.30 ms │ 3194.95 ms │ 1.11x slower │
│ QQuery 10 │ 634.94 ms │ 644.54 ms │ no change │
│ QQuery 11 │ 2260.97 ms │ 2127.60 ms │ +1.06x faster │
│ QQuery 12 │ 189.48 ms │ 188.30 ms │ no change │
│ QQuery 13 │ 545.43 ms │ 549.45 ms │ no change │
└───────────┴─────────────┴───────────────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary ┃ ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD) │ 21653.60ms │
│ Total Time (claude_single-distinct-to-groupby-allow-count) │ 21169.11ms │
│ Average Time (HEAD) │ 1546.69ms │
│ Average Time (claude_single-distinct-to-groupby-allow-count) │ 1512.08ms │
│ Queries Faster │ 2 │
│ Queries Slower │ 1 │
│ Queries with No Change │ 11 │
│ Queries with Failure │ 0 │
└──────────────────────────────────────────────────────────────┴────────────┘
Distribution per query (min / mean ±stddev / max):
Comparing HEAD and claude_single-distinct-to-groupby-allow-count
--------------------
Benchmark clickbench_extended.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query ┃ HEAD ┃ claude_single-distinct-to-groupby-allow-count ┃ Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 0 │ 780.26 / 788.40 ±7.00 / 798.02 ms │ 783.34 / 796.55 ±10.67 / 809.75 ms │ no change │
│ QQuery 1 │ 188.77 / 190.78 ±3.82 / 198.42 ms │ 188.75 / 194.28 ±5.60 / 204.62 ms │ no change │
│ QQuery 2 │ 449.28 / 453.47 ±2.78 / 457.12 ms │ 447.96 / 452.87 ±2.56 / 455.13 ms │ no change │
│ QQuery 3 │ 314.27 / 315.33 ±0.55 / 315.88 ms │ 313.49 / 315.12 ±1.11 / 316.35 ms │ no change │
│ QQuery 4 │ 1149.65 / 1161.28 ±8.39 / 1169.85 ms │ 1159.99 / 1169.81 ±9.52 / 1186.09 ms │ no change │
│ QQuery 5 │ 11158.56 / 11546.89 ±385.86 / 12210.52 ms │ 10462.77 / 11461.59 ±673.70 / 12207.74 ms │ no change │
│ QQuery 6 │ 2.66 / 2.90 ±0.30 / 3.49 ms │ 2.64 / 2.85 ±0.31 / 3.47 ms │ no change │
│ QQuery 7 │ 689.69 / 757.69 ±57.76 / 848.42 ms │ 690.22 / 740.91 ±43.04 / 806.86 ms │ no change │
│ QQuery 8 │ 407.32 / 439.56 ±27.83 / 472.94 ms │ 415.12 / 432.21 ±33.30 / 498.81 ms │ no change │
│ QQuery 9 │ 2882.30 / 2984.86 ±74.40 / 3111.76 ms │ 3194.95 / 3221.07 ±20.22 / 3251.69 ms │ 1.08x slower │
│ QQuery 10 │ 634.94 / 688.29 ±74.43 / 830.48 ms │ 644.54 / 662.63 ±20.03 / 700.78 ms │ no change │
│ QQuery 11 │ 2260.97 / 2394.00 ±67.19 / 2439.08 ms │ 2127.60 / 2229.93 ±71.74 / 2292.79 ms │ +1.07x faster │
│ QQuery 12 │ 189.48 / 211.00 ±17.54 / 242.62 ms │ 188.30 / 238.67 ±92.46 / 423.49 ms │ 1.13x slower │
│ QQuery 13 │ 545.43 / 555.20 ±7.18 / 564.94 ms │ 549.45 / 567.57 ±16.61 / 590.64 ms │ no change │
└───────────┴───────────────────────────────────────────┴───────────────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary ┃ ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD) │ 22489.65ms │
│ Total Time (claude_single-distinct-to-groupby-allow-count) │ 22486.06ms │
│ Average Time (HEAD) │ 1606.40ms │
│ Average Time (claude_single-distinct-to-groupby-allow-count) │ 1606.15ms │
│ Queries Faster │ 1 │
│ Queries Slower │ 2 │
│ Queries with No Change │ 11 │
│ Queries with Failure │ 0 │
└──────────────────────────────────────────────────────────────┴────────────┘

Resource Usage

clickbench_extended — base (merge-base)

MetricValue
Wall time115.0s
Peak memory11.8 GiB
Avg memory5.2 GiB
CPU user1033.4s
CPU sys49.0s
Peak spill0 B

clickbench_extended — branch

MetricValue
Wall time115.0s
Peak memory11.6 GiB
Avg memory5.1 GiB
CPU user1030.0s
CPU sys50.8s
Peak spill0 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 claude/single-distinct-to-groupby-allow-count (3d3cf6b) to e1ca94f (merge-base) diff

Run configuration
run benchmark clickbench_extended
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 claude_single-distinct-to-groupby-allow-count
--------------------
Benchmark clickbench_extended.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query ┃ HEAD ┃ claude_single-distinct-to-groupby-allow-count ┃ Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 0 │ 813.01 ms │ 777.37 ms │ no change │
│ QQuery 1 │ 188.04 ms │ 188.25 ms │ no change │
│ QQuery 2 │ 449.27 ms │ 449.91 ms │ no change │
│ QQuery 3 │ 314.49 ms │ 316.07 ms │ no change │
│ QQuery 4 │ 1142.10 ms │ 1148.43 ms │ no change │
│ QQuery 5 │ 10986.58 ms │ 10600.59 ms │ no change │
│ QQuery 6 │ 2.63 ms │ 2.76 ms │ no change │
│ QQuery 7 │ 693.61 ms │ 692.21 ms │ no change │
│ QQuery 8 │ 410.71 ms │ 423.30 ms │ no change │
│ QQuery 9 │ 2948.99 ms │ 3231.74 ms │ 1.10x slower │
│ QQuery 10 │ 623.52 ms │ 643.95 ms │ no change │
│ QQuery 11 │ 2234.22 ms │ 2120.47 ms │ +1.05x faster │
│ QQuery 12 │ 186.08 ms │ 194.65 ms │ no change │
│ QQuery 13 │ 535.40 ms │ 543.05 ms │ no change │
└───────────┴─────────────┴───────────────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary ┃ ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD) │ 21528.64ms │
│ Total Time (claude_single-distinct-to-groupby-allow-count) │ 21332.74ms │
│ Average Time (HEAD) │ 1537.76ms │
│ Average Time (claude_single-distinct-to-groupby-allow-count) │ 1523.77ms │
│ Queries Faster │ 1 │
│ Queries Slower │ 1 │
│ Queries with No Change │ 12 │
│ Queries with Failure │ 0 │
└──────────────────────────────────────────────────────────────┴────────────┘
Distribution per query (min / mean ±stddev / max):
Comparing HEAD and claude_single-distinct-to-groupby-allow-count
--------------------
Benchmark clickbench_extended.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ Query ┃ HEAD ┃ claude_single-distinct-to-groupby-allow-count ┃ Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ QQuery 0 │ 813.01 / 830.45 ±10.39 / 841.65 ms │ 777.37 / 797.48 ±14.15 / 816.71 ms │ no change │
│ QQuery 1 │ 188.04 / 190.24 ±3.54 / 197.29 ms │ 188.25 / 192.24 ±6.75 / 205.73 ms │ no change │
│ QQuery 2 │ 449.27 / 457.13 ±5.59 / 464.90 ms │ 449.91 / 451.88 ±1.56 / 454.33 ms │ no change │
│ QQuery 3 │ 314.49 / 316.01 ±0.86 / 316.87 ms │ 316.07 / 317.54 ±0.95 / 318.81 ms │ no change │
│ QQuery 4 │ 1142.10 / 1181.35 ±27.19 / 1226.11 ms │ 1148.43 / 1161.71 ±7.92 / 1172.70 ms │ no change │
│ QQuery 5 │ 10986.58 / 11188.78 ±179.01 / 11421.09 ms │ 10600.59 / 11157.86 ±458.18 / 11827.43 ms │ no change │
│ QQuery 6 │ 2.63 / 2.98 ±0.35 / 3.64 ms │ 2.76 / 2.99 ±0.32 / 3.58 ms │ no change │
│ QQuery 7 │ 693.61 / 755.50 ±65.69 / 869.85 ms │ 692.21 / 726.53 ±22.67 / 762.57 ms │ no change │
│ QQuery 8 │ 410.71 / 420.67 ±6.27 / 427.32 ms │ 423.30 / 446.36 ±27.48 / 496.79 ms │ 1.06x slower │
│ QQuery 9 │ 2948.99 / 3003.73 ±36.37 / 3054.82 ms │ 3231.74 / 3280.54 ±40.86 / 3348.53 ms │ 1.09x slower │
│ QQuery 10 │ 623.52 / 701.15 ±77.12 / 843.95 ms │ 643.95 / 713.73 ±101.38 / 915.04 ms │ no change │
│ QQuery 11 │ 2234.22 / 2350.58 ±63.81 / 2414.74 ms │ 2120.47 / 2253.85 ±100.25 / 2359.84 ms │ no change │
│ QQuery 12 │ 186.08 / 223.71 ±68.71 / 361.01 ms │ 194.65 / 219.95 ±31.15 / 279.52 ms │ no change │
│ QQuery 13 │ 535.40 / 550.32 ±9.48 / 563.50 ms │ 543.05 / 569.95 ±25.23 / 606.73 ms │ no change │
└───────────┴───────────────────────────────────────────┴───────────────────────────────────────────────┴──────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary ┃ ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD) │ 22172.62ms │
│ Total Time (claude_single-distinct-to-groupby-allow-count) │ 22292.60ms │
│ Average Time (HEAD) │ 1583.76ms │
│ Average Time (claude_single-distinct-to-groupby-allow-count) │ 1592.33ms │
│ Queries Faster │ 0 │
│ Queries Slower │ 2 │
│ Queries with No Change │ 12 │
│ Queries with Failure │ 0 │
└──────────────────────────────────────────────────────────────┴────────────┘

Resource Usage

clickbench_extended — base (merge-base)

MetricValue
Wall time115.0s
Peak memory12.2 GiB
Avg memory5.0 GiB
CPU user1033.7s
CPU sys47.2s
Peak spill0 B

clickbench_extended — branch

MetricValue
Wall time115.0s
Peak memory11.8 GiB
Avg memory5.0 GiB
CPU user1036.0s
CPU sys50.6s
Peak spill0 B

File an issue against this benchmark runner

@adriangb
adriangbforce-pushed the claude/single-distinct-to-groupby-allow-count branch from 3d3cf6b to e0e5a8cCompareSeptember 7, 2026 14:18
@adriangb

Copy link
Copy Markdown
ContributorAuthor

@kosiew breaking out the benchmark in #25026 + a fix to our benchmarks in #25027 that I found as a drive by

adriangband others added 5 commits September 7, 2026 12:08
`SingleDistinctToGroupBy` rewrites `AGG(DISTINCT x)` into a two phase
group by, which is what keeps a high cardinality distinct off the
one-accumulator-per-group path in `GroupsAccumulatorAdapter`. The rule
tolerated a non-distinct `sum`, `min` or `max` next to the distinct
aggregate but bailed out on `count`, so the very common
`count(*), count(DISTINCT x) ... GROUP BY` shape kept the unrewritten
plan and its memory profile.
Allow a non-distinct `count` as well. `count` is the one supported
function whose outer phase is a different function: the inner group by
counts the rows of each `(group, distinct value)` partition and the
outer phase adds those partial counts up with `sum`, since count over a
group is the sum of the counts of any partition of that group.
Two details follow from that substitution:
- `count` and `sum` are resolved from the session function registry and
the rewrite only fires when the aggregate is that exact `count`, so a
session without a registry or with its own `count` is left alone.
- `count` returns a non-null 0 over an empty input while `sum` of no
rows is NULL, which is reachable for an aggregate with no group by.
The projection selects
`CASE WHEN sum(alias) IS NOT NULL THEN sum(alias) ELSE 0 END`, which
restores the 0 and keeps the column's type and nullability as `count`
had them.
The new sqllogictest file asserts every result twice, once with the
optimizer disabled and once with it enabled, over data with NULL and
all-NULL distinct values, an empty input, and `count(*)` versus
`count(col)` versus `count(1)`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`aggregate_distinct_with_having` round trips
`SELECT a, count(distinct b) ... HAVING count(b) > 100` through substrait and
asserts the plan comes back displaying identically. It passed only because the
non-distinct `count` made `SingleDistinctToGroupBy` bail out, so the plan had no
aliases in it. With the rule now allowing that `count`, the query is rewritten
and the assertion fails.
The failure is a pre-existing substrait gap rather than anything specific to
this query: substrait carries no names for an aggregate's grouping and measure
expressions, so the consumer derives them from the expressions themselves and
the `alias1` and `alias2` names the rule introduces are lost. Any plan the rule
rewrites fails the same way, including the plain
`SELECT a, count(distinct b) FROM data GROUP BY a, c` that this change does not
touch.
Remove the rule from the session used by this one test, so it keeps covering the
un-rewritten aggregate it was written for instead of depending on the rule
bailing out.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A memory limited ClickBench run contradicted the memory rationale for the
case it measures. Q22 is `SELECT "SearchPhrase", MIN("URL"), MIN("Title"),
COUNT(*), COUNT(DISTINCT "UserID") ... GROUP BY "SearchPhrase"`, the only
plan the change touched, and its peak memory pool reservation went from
3.1 MiB to 7.3 MiB, up 132.2%, with neighbouring queries moving by a few
percent either way.
`UserID` is an `Int64`, and `Count::groups_accumulator_supported` returns
true for every integer type and false for everything else. So the base
plan already had `PrimitiveDistinctCountGroupsAccumulator` and never went
near `GroupsAccumulatorAdapter`. The rewrite replaced a compact vectorized
accumulator with an inner group by on `(SearchPhrase, UserID)`, which also
carries `min("URL")` and `min("Title")` at that much finer grain, and
bought nothing back.
Measured locally on 4M rows, 500k groups and 2M distinct pairs, peak pool
reservation for `SELECT g, count(*), count(DISTINCT x) FROM t GROUP BY g`:
x BIGINT 135 MiB unrewritten, 205 MiB rewritten
x VARCHAR 13.6 GiB unrewritten, 255 MiB rewritten
Peak RSS for the VARCHAR pair was 18.0 GiB against 433 MiB, so the pool
figure is real memory rather than an accounting artifact. The rewrite pays
exactly when the distinct argument would otherwise land on the adapter.
`datafusion-optimizer` cannot depend on `datafusion-functions-aggregate` to
read that list of types, and has no `AccumulatorArgs` to ask
`groups_accumulator_supported` with. Its `Cargo.toml` names the way out, so
this adds `AggregateUDFImpl::groups_accumulator_supported_for_types`,
defaulting to false as `groups_accumulator_supported` does. `Count` is the
only implementor and its physical method now delegates to it, so the two
cannot disagree.
The gate covers only the `count` this branch added. A plan that already
qualified through a non-distinct `sum`, `min` or `max` is rewritten exactly
as before, over any distinct argument type.
The ClickBench snapshot returns to its base form, so no existing snapshot
in the repository changes. `single_distinct_to_groupby.slt` now carries the
same values in a `VARCHAR` and an `INT` column and asserts both sides of
the gate, still under both `datafusion.optimizer.max_passes = 0` and the
default.
`cargo doc` runs with `-D warnings`, and a doc link from the public
`SingleDistinctToGroupBy` to the private `rewrite_pays_for_count` is an
error. Say the same thing in prose instead.
`AggregateUDFImpl::groups_accumulator_supported_for_types` returned `bool`
with a `false` default, and `rewrite_pays_for_count` read `false` as
"the rewrite pays". Every aggregate that did not override the method was
therefore waved through the gate, which is the permissive answer rather
than the safe one.
Return `Option<bool>` instead, defaulting to `None`, and rewrite only on
`Some(false)`. `None` says the aggregate does not answer the question, and
silence is not evidence that the rewrite pays.
Measured on unmodified upstream, over 4,000,000 rows in 2,000 groups,
`SELECT g, count(*), sum(DISTINCT int_col) FROM t GROUP BY g` reached the
gated path and regressed 3.15x: 71.0 MiB unrewritten against 223.4 MiB
rewritten. `min(DISTINCT x)` reached it too, and regresses much further,
because `min(DISTINCT x)` is `min(x)` and the unrewritten plan keeps one
scalar per group.
`Count` is the only implementor and its answers are unchanged, so no plan
that the gate already allowed changes shape.
@adriangb
adriangbforce-pushed the claude/single-distinct-to-groupby-allow-count branch from e0e5a8c to 07f24b6CompareSeptember 7, 2026 17:10
adriangb added a commit to pydantic/datafusion that referenced this pull request Sep 7, 2026
…over a string (apache#25026)
## Which issue does this PR close?
No existing issue. This is benchmark coverage carved out of apache#24859 so
that it can land first: a benchmark query added in the same PR that
needs it cannot appear in an A/B run, because the bot compares against
the merge base and the merge base does not have the query.
## Rationale for this change
`COUNT(DISTINCT)` has a specialized `GroupsAccumulator` for the integer
types and for no other type. Every other type falls back to
`GroupsAccumulatorAdapter`, which holds one boxed `Accumulator`, and
therefore one hash table, for each group. The cost of that fallback is
per group, so the group cardinality is what decides how much it costs.
Nothing in either suite measures that:
- Extended Q2 is the only query that puts a `COUNT(DISTINCT)` on a
non-integer column at all. It groups by `BrowserCountry`, so it
exercises the adapter at a low group cardinality, which is where the
adapter is cheapest.
- Standard Q8, Q10, Q11 and Q13 hold a distinct aggregate that stands
alone, so `SingleDistinctToGroupBy` already rewrites them and they never
reach the adapter.
- Standard Q9 carries an `AVG`, which that rule has never accepted.
- Standard Q22 is the one query that pairs a lone distinct aggregate
with a non-distinct count, and it counts distinct `UserID`, an `Int64`,
which has a specialized accumulator.
So a lone `COUNT(DISTINCT <string>)` grouped by a high cardinality key,
next to a non-distinct `COUNT(*)`, is uncovered. That is an ordinary
analytics shape, it is the shape apache#24857 changed the memory profile of,
and it is the shape apache#24859 proposes to rewrite. Neither of those could
show its effect on any benchmark in this repository.
## What changes are included in this PR?
The query:
```sql
SELECT "SearchPhrase", COUNT(*) AS c, COUNT(DISTINCT "MobilePhoneModel") AS models
FROM hits
WHERE "SearchPhrase" <> ''
GROUP BY "SearchPhrase"
ORDER BY c DESC
LIMIT 10;
```
The extended ClickBench queries live in three places, and a query added
to only one of them is invisible to the others. This PR adds it to all
three:
- `benchmarks/queries/clickbench/extended/q14.sql`, which feeds `dfbench
clickbench --queries-path`. Queries are discovered from the directory,
so the runner needs no change.
-
`benchmarks/sql_benchmarks/clickbench_extended/benchmarks/q14.benchmark`,
which feeds `benchmark_runner clickbench_extended`. Structurally
identical to `q13.benchmark` apart from the query. Suite files are also
discovered from the directory; `benchmark_runner clickbench_extended
--list` now reports 15 queries.
- `datafusion/sqllogictest/test_files/clickbench_extended.slt`, which
runs the extended queries against the committed ten row
`clickbench_hits_10.parquet` fixture.
That last file had only ever mirrored q0 through q6. q7 through q13 were
added to the queries directory without a matching sqllogictest entry,
and q14 would have widened that gap, so this backfills q7 through q14
together. That is why the diff is larger than one query.
Having to add one query in three places is itself the problem, and the
copies have already drifted: extended q6 carries a cast in its
`sql_benchmarks` copy that the other two do not have. I filed apache#25031 for
that; it is out of scope here.
There is one pre-existing staleness this PR does not fix.
`datafusion/core/benches/sql_planner.rs` builds its ClickBench planning
set from a hardcoded `(0..=7)` over the extended directory, so extended
Q8 through Q13 were already outside it before this PR and Q14 joins
them. That is a separate cleanup.
## What is the testing strategy for this PR?
The sqllogictest entries are the test. `clickbench_extended.slt` runs
every extended query against the committed ten row fixture and asserts
its output, so q7 through q14 are now executed on every CI run rather
than only by whoever runs the benchmark suite by hand. `cargo test -p
datafusion-sqllogictest --test sqllogictests -- clickbench` passes, 2 of
2 files.
`cargo test -p datafusion-benchmarks` passes, 192 tests, and
`benchmark_runner clickbench_extended --list` reports the suite at 15
queries, confirming the new `.benchmark` file parses and is discovered.
I do not have a `hits.parquet` to hand, so I have not run the query
against the full dataset. The plan shape, which is the whole value of
the query, was checked separately: on an equivalent local table with the
same filter, grouping, `COUNT(*)` and string `COUNT(DISTINCT)`, the
aggregate keeps `aggr=[[count(Int64(1)), count(DISTINCT ...)]]`, which
is the `GroupsAccumulatorAdapter` path this query exists to measure, and
it is not rewritten away.
What I have not established is the effect size on the real dataset,
since that depends on how many distinct `SearchPhrase` values survive
the filter. If a reviewer with the data runs it, that number is worth
having on this PR.
`ci/scripts/doc_prettier_check.sh` passes on the README change.
## Are there any user-facing changes?
No. This adds a benchmark query and documentation only.
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@adriangb

Copy link
Copy Markdown
ContributorAuthor

run benchmark clickbench_extended clickbench_extended clickbench_extended

env:
DATAFUSION_RUNTIME_MEMORY_LIMIT: 16G

@adriangb

Copy link
Copy Markdown
ContributorAuthor

show benchmark queue

@adriangbot

Copy link
Copy Markdown

Hi @adriangb, you asked to view the benchmark queue (#24859 (comment)).

CommentRepoPRUserBenchmarksStatus
#5573802227apache/datafusion#24859adriangb["clickbench_extended"]running
#5573802227apache/datafusion#24859adriangb["clickbench_extended"]running
#5573802227apache/datafusion#24859adriangb["clickbench_extended"]running

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-c5573802227-2206-qsxz8 6.12.94+ #1 SMP Fri Jul 17 09:42:57 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 claude/single-distinct-to-groupby-allow-count (07f24b6) to 16ace4f (merge-base) diff

Run configuration
run benchmark clickbench_extendedenv:
DATAFUSION_RUNTIME_MEMORY_LIMIT: "16G"

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-c5573802227-2207-k5kvb 6.12.94+ #1 SMP Fri Jul 17 09:42:57 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 claude/single-distinct-to-groupby-allow-count (07f24b6) to 16ace4f (merge-base) diff

Run configuration
run benchmark clickbench_extendedenv:
DATAFUSION_RUNTIME_MEMORY_LIMIT: "16G"

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-c5573802227-2205-jcl4f 6.12.94+ #1 SMP Fri Jul 17 09:42:57 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 claude/single-distinct-to-groupby-allow-count (07f24b6) to 16ace4f (merge-base) diff

Run configuration
run benchmark clickbench_extendedenv:
DATAFUSION_RUNTIME_MEMORY_LIMIT: "16G"

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 claude/single-distinct-to-groupby-allow-count (07f24b6) to 16ace4f (merge-base) diff

Run configuration
run benchmark clickbench_extendedenv:
DATAFUSION_RUNTIME_MEMORY_LIMIT: "16G"
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 claude_single-distinct-to-groupby-allow-count
--------------------
Benchmark clickbench_extended.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query ┃ HEAD ┃ claude_single-distinct-to-groupby-allow-count ┃ Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 0 │ 757.16 ms │ 781.13 ms │ no change │
│ QQuery 1 │ 188.91 ms │ 190.00 ms │ no change │
│ QQuery 2 │ 453.34 ms │ 451.41 ms │ no change │
│ QQuery 3 │ 313.91 ms │ 316.10 ms │ no change │
│ QQuery 4 │ 1128.57 ms │ 1141.32 ms │ no change │
│ QQuery 5 │ 17994.72 ms │ 18120.21 ms │ no change │
│ QQuery 6 │ 2.69 ms │ 2.61 ms │ no change │
│ QQuery 7 │ 662.47 ms │ 668.71 ms │ no change │
│ QQuery 8 │ 416.00 ms │ 413.32 ms │ no change │
│ QQuery 9 │ 2672.71 ms │ 2732.93 ms │ no change │
│ QQuery 10 │ 635.32 ms │ 634.05 ms │ no change │
│ QQuery 11 │ 2330.55 ms │ 2243.59 ms │ no change │
│ QQuery 12 │ 187.47 ms │ 190.77 ms │ no change │
│ QQuery 13 │ 548.08 ms │ 552.43 ms │ no change │
│ QQuery 14 │ 2471.40 ms │ 546.00 ms │ +4.53x faster │
└───────────┴─────────────┴───────────────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary ┃ ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD) │ 30763.30ms │
│ Total Time (claude_single-distinct-to-groupby-allow-count) │ 28984.58ms │
│ Average Time (HEAD) │ 2050.89ms │
│ Average Time (claude_single-distinct-to-groupby-allow-count) │ 1932.31ms │
│ Queries Faster │ 1 │
│ Queries Slower │ 0 │
│ Queries with No Change │ 14 │
│ Queries with Failure │ 0 │
└──────────────────────────────────────────────────────────────┴────────────┘
Distribution per query (min / mean ±stddev / max):
Comparing HEAD and claude_single-distinct-to-groupby-allow-count
--------------------
Benchmark clickbench_extended.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query ┃ HEAD ┃ claude_single-distinct-to-groupby-allow-count ┃ Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 0 │ 757.16 / 770.50 ±10.17 / 785.26 ms │ 781.13 / 795.13 ±9.29 / 809.50 ms │ no change │
│ QQuery 1 │ 188.91 / 191.41 ±3.20 / 197.75 ms │ 190.00 / 190.52 ±0.46 / 191.08 ms │ no change │
│ QQuery 2 │ 453.34 / 456.62 ±2.60 / 459.57 ms │ 451.41 / 454.89 ±2.02 / 457.39 ms │ no change │
│ QQuery 3 │ 313.91 / 315.47 ±0.97 / 316.75 ms │ 316.10 / 317.76 ±1.08 / 319.24 ms │ no change │
│ QQuery 4 │ 1128.57 / 1150.51 ±15.01 / 1174.14 ms │ 1141.32 / 1151.15 ±10.63 / 1165.50 ms │ no change │
│ QQuery 5 │ 17994.72 / 18400.60 ±253.34 / 18711.10 ms │ 18120.21 / 18754.14 ±510.55 / 19288.80 ms │ no change │
│ QQuery 6 │ 2.69 / 2.90 ±0.29 / 3.47 ms │ 2.61 / 2.84 ±0.37 / 3.57 ms │ no change │
│ QQuery 7 │ 662.47 / 744.05 ±53.47 / 830.47 ms │ 668.71 / 725.76 ±31.09 / 754.32 ms │ no change │
│ QQuery 8 │ 416.00 / 432.46 ±22.68 / 477.28 ms │ 413.32 / 445.81 ±44.75 / 530.08 ms │ no change │
│ QQuery 9 │ 2672.71 / 2853.13 ±108.13 / 2960.09 ms │ 2732.93 / 2856.65 ±63.01 / 2902.26 ms │ no change │
│ QQuery 10 │ 635.32 / 670.79 ±20.75 / 697.31 ms │ 634.05 / 667.64 ±44.08 / 753.85 ms │ no change │
│ QQuery 11 │ 2330.55 / 2381.20 ±37.51 / 2425.01 ms │ 2243.59 / 2285.15 ±22.83 / 2311.16 ms │ no change │
│ QQuery 12 │ 187.47 / 229.90 ±75.39 / 380.52 ms │ 190.77 / 218.20 ±43.41 / 304.55 ms │ +1.05x faster │
│ QQuery 13 │ 548.08 / 569.99 ±16.41 / 598.93 ms │ 552.43 / 558.91 ±4.97 / 567.12 ms │ no change │
│ QQuery 14 │ 2471.40 / 2529.67 ±38.65 / 2583.56 ms │ 546.00 / 624.86 ±65.79 / 717.10 ms │ +4.05x faster │
└───────────┴───────────────────────────────────────────┴───────────────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary ┃ ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD) │ 31699.20ms │
│ Total Time (claude_single-distinct-to-groupby-allow-count) │ 30049.42ms │
│ Average Time (HEAD) │ 2113.28ms │
│ Average Time (claude_single-distinct-to-groupby-allow-count) │ 2003.29ms │
│ Queries Faster │ 2 │
│ Queries Slower │ 0 │
│ Queries with No Change │ 13 │
│ Queries with Failure │ 0 │
└──────────────────────────────────────────────────────────────┴────────────┘

Memory Pool Peaks

Peak MemoryPool reservation per query — what DataFusion's accounting believes it reserved. Recorded only when the benchmark runs with DATAFUSION_RUNTIME_MEMORY_LIMIT set.

Base: 16ace4f (merge-base) | Changed: claude/single-distinct-to-groupby-allow-count

clickbench_extended

QueryBaseChangedChange
Query 0844.5 MiB863.0 MiB+2.2%
Query 12.4 MiB2.4 MiB+0.0%
Query 211.7 MiB11.7 MiB+0.0%
Query 311.7 MiB11.7 MiB+0.0%
Query 44.2 GiB4.2 GiB+0.0%
Query 55.5 GiB5.5 GiB-0.1%
Query 6104 B104 B+0.0%
Query 74.9 GiB4.9 GiB-0.1%
Query 837.0 MiB37.0 MiB+0.0%
Query 94.8 GiB4.7 GiB-1.8%
Query 102.3 MiB1.9 MiB-17.9%
Query 112.8 GiB2.8 GiB-0.2%
Query 121.1 MiB1.4 MiB+23.8%
Query 13520 B520 B+0.0%
Query 144.4 GiB1.3 GiB-70.5%

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.

BenchmarkSideMax pool peakPeak RSSGapRSS / pool
clickbench_extendedbase (16ace4f (merge-base))5.5 GiB23.1 GiB17.6 GiB4.2×
clickbench_extendedchanged (claude/single-distinct-to-groupby-allow-count)5.5 GiB24.3 GiB18.8 GiB4.4×
Resource Usage

clickbench_extended — base (merge-base)

MetricValue
Wall time160.0s
Peak memory23.1 GiB
Avg memory5.5 GiB
CPU user1510.3s
CPU sys90.3s
Peak spill0 B

clickbench_extended — branch

MetricValue
Wall time155.0s
Peak memory24.3 GiB
Avg memory5.3 GiB
CPU user1441.7s
CPU sys86.2s
Peak spill0 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 claude/single-distinct-to-groupby-allow-count (07f24b6) to 16ace4f (merge-base) diff

Run configuration
run benchmark clickbench_extendedenv:
DATAFUSION_RUNTIME_MEMORY_LIMIT: "16G"
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 claude_single-distinct-to-groupby-allow-count
--------------------
Benchmark clickbench_extended.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query ┃ HEAD ┃ claude_single-distinct-to-groupby-allow-count ┃ Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 0 │ 794.65 ms │ 794.53 ms │ no change │
│ QQuery 1 │ 190.93 ms │ 188.99 ms │ no change │
│ QQuery 2 │ 454.77 ms │ 450.98 ms │ no change │
│ QQuery 3 │ 317.74 ms │ 315.45 ms │ no change │
│ QQuery 4 │ 1135.20 ms │ 1123.91 ms │ no change │
│ QQuery 5 │ 18139.02 ms │ 18512.60 ms │ no change │
│ QQuery 6 │ 2.63 ms │ 2.65 ms │ no change │
│ QQuery 7 │ 653.73 ms │ 679.09 ms │ no change │
│ QQuery 8 │ 417.15 ms │ 417.62 ms │ no change │
│ QQuery 9 │ 2680.54 ms │ 2784.34 ms │ no change │
│ QQuery 10 │ 649.65 ms │ 646.44 ms │ no change │
│ QQuery 11 │ 2314.52 ms │ 2264.08 ms │ no change │
│ QQuery 12 │ 187.16 ms │ 190.06 ms │ no change │
│ QQuery 13 │ 550.94 ms │ 545.39 ms │ no change │
│ QQuery 14 │ 2533.55 ms │ 575.33 ms │ +4.40x faster │
└───────────┴─────────────┴───────────────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary ┃ ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD) │ 31022.18ms │
│ Total Time (claude_single-distinct-to-groupby-allow-count) │ 29491.46ms │
│ Average Time (HEAD) │ 2068.15ms │
│ Average Time (claude_single-distinct-to-groupby-allow-count) │ 1966.10ms │
│ Queries Faster │ 1 │
│ Queries Slower │ 0 │
│ Queries with No Change │ 14 │
│ Queries with Failure │ 0 │
└──────────────────────────────────────────────────────────────┴────────────┘
Distribution per query (min / mean ±stddev / max):
Comparing HEAD and claude_single-distinct-to-groupby-allow-count
--------------------
Benchmark clickbench_extended.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query ┃ HEAD ┃ claude_single-distinct-to-groupby-allow-count ┃ Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 0 │ 794.65 / 805.69 ±11.35 / 826.82 ms │ 794.53 / 806.07 ±11.58 / 826.90 ms │ no change │
│ QQuery 1 │ 190.93 / 194.26 ±5.15 / 204.52 ms │ 188.99 / 189.80 ±0.58 / 190.59 ms │ no change │
│ QQuery 2 │ 454.77 / 460.02 ±2.91 / 462.78 ms │ 450.98 / 454.83 ±6.58 / 467.89 ms │ no change │
│ QQuery 3 │ 317.74 / 319.55 ±0.92 / 320.25 ms │ 315.45 / 316.62 ±0.83 / 317.87 ms │ no change │
│ QQuery 4 │ 1135.20 / 1156.41 ±15.98 / 1181.15 ms │ 1123.91 / 1157.15 ±27.73 / 1188.48 ms │ no change │
│ QQuery 5 │ 18139.02 / 18693.77 ±458.61 / 19387.58 ms │ 18512.60 / 18799.19 ±287.20 / 19211.52 ms │ no change │
│ QQuery 6 │ 2.63 / 2.92 ±0.35 / 3.58 ms │ 2.65 / 2.90 ±0.25 / 3.37 ms │ no change │
│ QQuery 7 │ 653.73 / 708.65 ±36.13 / 760.90 ms │ 679.09 / 734.37 ±35.46 / 774.99 ms │ no change │
│ QQuery 8 │ 417.15 / 437.23 ±30.45 / 497.78 ms │ 417.62 / 454.35 ±62.10 / 578.36 ms │ no change │
│ QQuery 9 │ 2680.54 / 2905.35 ±117.53 / 3013.57 ms │ 2784.34 / 2843.09 ±48.25 / 2918.64 ms │ no change │
│ QQuery 10 │ 649.65 / 669.40 ±19.36 / 695.36 ms │ 646.44 / 666.95 ±15.96 / 688.53 ms │ no change │
│ QQuery 11 │ 2314.52 / 2396.04 ±41.81 / 2428.78 ms │ 2264.08 / 2300.30 ±32.20 / 2345.94 ms │ no change │
│ QQuery 12 │ 187.16 / 212.93 ±42.76 / 298.27 ms │ 190.06 / 221.10 ±52.54 / 325.88 ms │ no change │
│ QQuery 13 │ 550.94 / 570.80 ±18.45 / 600.06 ms │ 545.39 / 582.66 ±36.38 / 648.87 ms │ no change │
│ QQuery 14 │ 2533.55 / 2588.73 ±44.49 / 2666.85 ms │ 575.33 / 624.47 ±47.71 / 714.36 ms │ +4.15x faster │
└───────────┴───────────────────────────────────────────┴───────────────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary ┃ ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD) │ 32121.74ms │
│ Total Time (claude_single-distinct-to-groupby-allow-count) │ 30153.86ms │
│ Average Time (HEAD) │ 2141.45ms │
│ Average Time (claude_single-distinct-to-groupby-allow-count) │ 2010.26ms │
│ Queries Faster │ 1 │
│ Queries Slower │ 0 │
│ Queries with No Change │ 14 │
│ Queries with Failure │ 0 │
└──────────────────────────────────────────────────────────────┴────────────┘

Memory Pool Peaks

Peak MemoryPool reservation per query — what DataFusion's accounting believes it reserved. Recorded only when the benchmark runs with DATAFUSION_RUNTIME_MEMORY_LIMIT set.

Base: 16ace4f (merge-base) | Changed: claude/single-distinct-to-groupby-allow-count

clickbench_extended

QueryBaseChangedChange
Query 0865.0 MiB855.0 MiB-1.2%
Query 12.4 MiB2.4 MiB+0.0%
Query 211.7 MiB11.7 MiB+0.0%
Query 311.7 MiB11.7 MiB+0.0%
Query 44.2 GiB4.2 GiB+0.0%
Query 55.5 GiB5.5 GiB+0.1%
Query 6104 B104 B+0.0%
Query 74.9 GiB4.9 GiB-0.0%
Query 838.7 MiB36.9 MiB-4.8%
Query 94.6 GiB4.6 GiB-0.2%
Query 102.1 MiB2.1 MiB+0.5%
Query 112.8 GiB2.9 GiB+2.3%
Query 121.4 MiB1.4 MiB+0.4%
Query 13520 B520 B+0.0%
Query 144.4 GiB1.3 GiB-69.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.

BenchmarkSideMax pool peakPeak RSSGapRSS / pool
clickbench_extendedbase (16ace4f (merge-base))5.5 GiB24.1 GiB18.6 GiB4.4×
clickbench_extendedchanged (claude/single-distinct-to-groupby-allow-count)5.5 GiB24.1 GiB18.6 GiB4.4×
Resource Usage

clickbench_extended — base (merge-base)

MetricValue
Wall time165.0s
Peak memory24.1 GiB
Avg memory5.3 GiB
CPU user1521.2s
CPU sys91.8s
Peak spill0 B

clickbench_extended — branch

MetricValue
Wall time155.0s
Peak memory24.1 GiB
Avg memory5.2 GiB
CPU user1443.3s
CPU sys89.6s
Peak spill0 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 claude/single-distinct-to-groupby-allow-count (07f24b6) to 16ace4f (merge-base) diff

Run configuration
run benchmark clickbench_extendedenv:
DATAFUSION_RUNTIME_MEMORY_LIMIT: "16G"
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 claude_single-distinct-to-groupby-allow-count
--------------------
Benchmark clickbench_extended.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query ┃ HEAD ┃ claude_single-distinct-to-groupby-allow-count ┃ Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 0 │ 1179.34 ms │ 1536.40 ms │ 1.30x slower │
│ QQuery 1 │ 194.86 ms │ 194.66 ms │ no change │
│ QQuery 2 │ 487.71 ms │ 489.31 ms │ no change │
│ QQuery 3 │ 340.23 ms │ 339.80 ms │ no change │
│ QQuery 4 │ 1617.08 ms │ 1689.44 ms │ no change │
│ QQuery 5 │ 23445.42 ms │ 23231.56 ms │ no change │
│ QQuery 6 │ 4.07 ms │ 4.11 ms │ no change │
│ QQuery 7 │ 1301.43 ms │ 1483.45 ms │ 1.14x slower │
│ QQuery 8 │ 555.36 ms │ 628.14 ms │ 1.13x slower │
│ QQuery 9 │ 4175.42 ms │ 4240.28 ms │ no change │
│ QQuery 10 │ 845.14 ms │ 835.28 ms │ no change │
│ QQuery 11 │ 3022.08 ms │ 2879.16 ms │ no change │
│ QQuery 12 │ 258.91 ms │ 259.58 ms │ no change │
│ QQuery 13 │ 719.32 ms │ 702.01 ms │ no change │
│ QQuery 14 │ 3909.36 ms │ 1164.88 ms │ +3.36x faster │
└───────────┴─────────────┴───────────────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary ┃ ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD) │ 42055.72ms │
│ Total Time (claude_single-distinct-to-groupby-allow-count) │ 39678.05ms │
│ Average Time (HEAD) │ 2803.71ms │
│ Average Time (claude_single-distinct-to-groupby-allow-count) │ 2645.20ms │
│ Queries Faster │ 1 │
│ Queries Slower │ 3 │
│ Queries with No Change │ 11 │
│ Queries with Failure │ 0 │
└──────────────────────────────────────────────────────────────┴────────────┘
Distribution per query (min / mean ±stddev / max):
Comparing HEAD and claude_single-distinct-to-groupby-allow-count
--------------------
Benchmark clickbench_extended.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query ┃ HEAD ┃ claude_single-distinct-to-groupby-allow-count ┃ Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 0 │ 1179.34 / 1430.53 ±130.10 / 1541.11 ms │ 1536.40 / 1601.36 ±41.80 / 1657.47 ms │ 1.12x slower │
│ QQuery 1 │ 194.86 / 195.95 ±1.40 / 198.72 ms │ 194.66 / 196.06 ±1.08 / 197.47 ms │ no change │
│ QQuery 2 │ 487.71 / 494.17 ±4.57 / 501.83 ms │ 489.31 / 496.79 ±8.86 / 510.46 ms │ no change │
│ QQuery 3 │ 340.23 / 344.04 ±3.37 / 348.61 ms │ 339.80 / 344.13 ±3.61 / 349.93 ms │ no change │
│ QQuery 4 │ 1617.08 / 1863.00 ±197.47 / 2138.51 ms │ 1689.44 / 1984.97 ±206.84 / 2218.09 ms │ 1.07x slower │
│ QQuery 5 │ 23445.42 / 24439.55 ±657.49 / 25419.58 ms │ 23231.56 / 24375.07 ±675.06 / 24968.54 ms │ no change │
│ QQuery 6 │ 4.07 / 4.30 ±0.25 / 4.77 ms │ 4.11 / 5.26 ±2.04 / 9.35 ms │ 1.22x slower │
│ QQuery 7 │ 1301.43 / 1462.16 ±103.00 / 1613.75 ms │ 1483.45 / 1614.90 ±133.45 / 1843.04 ms │ 1.10x slower │
│ QQuery 8 │ 555.36 / 666.23 ±110.82 / 879.48 ms │ 628.14 / 639.60 ±13.47 / 665.54 ms │ no change │
│ QQuery 9 │ 4175.42 / 4359.95 ±127.60 / 4517.43 ms │ 4240.28 / 4352.07 ±92.13 / 4463.23 ms │ no change │
│ QQuery 10 │ 845.14 / 986.59 ±242.37 / 1470.87 ms │ 835.28 / 956.96 ±135.44 / 1216.93 ms │ no change │
│ QQuery 11 │ 3022.08 / 3080.84 ±43.01 / 3138.75 ms │ 2879.16 / 2988.64 ±70.28 / 3087.70 ms │ no change │
│ QQuery 12 │ 258.91 / 312.18 ±80.72 / 471.34 ms │ 259.58 / 272.10 ±7.82 / 283.75 ms │ +1.15x faster │
│ QQuery 13 │ 719.32 / 766.69 ±57.02 / 867.70 ms │ 702.01 / 753.42 ±45.98 / 815.12 ms │ no change │
│ QQuery 14 │ 3909.36 / 4390.31 ±265.94 / 4696.26 ms │ 1164.88 / 1293.45 ±81.92 / 1374.22 ms │ +3.39x faster │
└───────────┴───────────────────────────────────────────┴───────────────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary ┃ ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD) │ 44796.49ms │
│ Total Time (claude_single-distinct-to-groupby-allow-count) │ 41874.80ms │
│ Average Time (HEAD) │ 2986.43ms │
│ Average Time (claude_single-distinct-to-groupby-allow-count) │ 2791.65ms │
│ Queries Faster │ 2 │
│ Queries Slower │ 4 │
│ Queries with No Change │ 9 │
│ Queries with Failure │ 0 │
└──────────────────────────────────────────────────────────────┴────────────┘

Memory Pool Peaks

Peak MemoryPool reservation per query — what DataFusion's accounting believes it reserved. Recorded only when the benchmark runs with DATAFUSION_RUNTIME_MEMORY_LIMIT set.

Base: 16ace4f (merge-base) | Changed: claude/single-distinct-to-groupby-allow-count

clickbench_extended

QueryBaseChangedChange
Query 0875.5 MiB873.5 MiB-0.2%
Query 12.4 MiB2.4 MiB+0.0%
Query 211.7 MiB11.7 MiB+0.0%
Query 311.7 MiB11.7 MiB+0.0%
Query 44.2 GiB4.2 GiB+0.0%
Query 55.5 GiB5.5 GiB+0.1%
Query 6104 B104 B+0.0%
Query 74.9 GiB4.9 GiB+0.1%
Query 840.2 MiB40.2 MiB+0.0%
Query 94.7 GiB4.7 GiB-0.7%
Query 102.5 MiB2.1 MiB-16.7%
Query 112.8 GiB2.9 GiB+5.0%
Query 121.4 MiB1.1 MiB-19.4%
Query 13520 B520 B+0.0%
Query 144.4 GiB1.3 GiB-70.3%

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.

BenchmarkSideMax pool peakPeak RSSGapRSS / pool
clickbench_extendedbase (16ace4f (merge-base))5.5 GiB24.0 GiB18.5 GiB4.4×
clickbench_extendedchanged (claude/single-distinct-to-groupby-allow-count)5.5 GiB23.8 GiB18.3 GiB4.3×
Resource Usage

clickbench_extended — base (merge-base)

MetricValue
Wall time230.1s
Peak memory24.0 GiB
Avg memory5.3 GiB
CPU user1989.6s
CPU sys202.0s
Peak spill0 B

clickbench_extended — branch

MetricValue
Wall time215.0s
Peak memory23.8 GiB
Avg memory5.1 GiB
CPU user1911.0s
CPU sys186.5s
Peak spill0 B

File an issue against this benchmark runner

@kosiewkosiew 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.

@adriangb
Thanks for working on this. The approach looks good to me, especially the conservative gating around specialized GroupsAccumulator support. I left two non-blocking suggestions for test coverage and a small API cleanup.

async fn aggregate_distinct_with_having() -> Result<()> {
roundtrip("SELECT a, count(distinct b) FROM data GROUP BY a, c HAVING count(b) > 100")
.await
let ctx = create_context_without_single_distinct_to_group_by().await?;

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 think using the custom context here makes sense. Substrait does not represent the aggregate/group-expression aliases introduced by this rewrite, so this looks like an existing plan-identity limitation rather than a semantic regression.

Would it be worth adding a separate test using the default context that exercises the rewritten plan through Substrait and checks its schema and query results instead of exact plan identity? This test could then stay focused on identity roundtripping with single_distinct_aggregation_to_group_by disabled.

/// and is left alone: narrowing it would change plans that have always been
/// rewritten, which no measurement here calls for.
fn rewrite_pays_for_count(
distinct_aggs: &[(&Arc<AggregateUDF>, &Vec<Expr>)],

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.

Small cleanup: I don't think this needs to require Vec<Expr>. Could this take slices instead, for example &[(&Arc<AggregateUDF>, &[Expr])], and use args.as_slice() when collecting? That seems a little more idiomatic and matches the surrounding slice-based APIs. No behavior change intended.

adriangband others added 3 commits September 8, 2026 07:34
The helper never needs the `Vec`, and the surrounding APIs pass slices.
No behaviour change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`aggregate_distinct_with_having` turns
`single_distinct_aggregation_to_group_by` off, because the aliases the
rule introduces have no Substrait representation and so a rewritten plan
does not round trip to an identical plan. That left the rewritten plan
uncovered.
Add a test on the default context that asserts what Substrait does
carry: the output schema and the rows. It pins the source plan shape
first, so it cannot quietly stop covering the rewrite.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"The production shape" and the table name `failed` came from the private
workload that motivated this rule change. Neither means anything to a
reader of an open source test file, and `failed` reads as a test failure
rather than as a join table.
Say what the queries actually cover instead.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment on lines +199 to +201
/// would open this path to every such function: `sum(DISTINCT int_col)` beside a
/// `count(*)` measured 3.15x the peak memory once rewritten, over 4,000,000 rows
/// in 2,000 groups.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Instead of citing numbers here which can quickly become outdated we should link to the PR where this was measured as a point in time artifact.

Link the measurements rather than quoting figures, which go stale and
cannot be checked from the source. Cut the paragraphs that restated the
rule's own code, and the inline comments that repeated what the doc block
above them already said. Keep the trait method's contract, since it is
public API, but not the narration around it.
26 comment lines net.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@adriangb

Copy link
Copy Markdown
ContributorAuthor

run benchmark clickbench_partitioned
env:
DATAFUSION_RUNTIME_MEMORY_LIMIT: 4G

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance:c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c5585614783-2212-wvrbr 6.12.94+ #1 SMP Fri Jul 17 09:42:57 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 claude/single-distinct-to-groupby-allow-count (173ae06) to 16ace4f (merge-base) diff

Run configuration
run benchmark clickbench_partitionedenv:
DATAFUSION_RUNTIME_MEMORY_LIMIT: "4G"

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 claude/single-distinct-to-groupby-allow-count (173ae06) to 16ace4f (merge-base) diff

Run configuration
run benchmark clickbench_partitionedenv:
DATAFUSION_RUNTIME_MEMORY_LIMIT: "4G"
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 claude_single-distinct-to-groupby-allow-count
--------------------
Benchmark clickbench_partitioned.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Query ┃ HEAD ┃ claude_single-distinct-to-groupby-allow-count ┃ Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ QQuery 0 │ 1.22 ms │ 1.25 ms │ no change │
│ QQuery 1 │ 11.68 ms │ 12.03 ms │ no change │
│ QQuery 2 │ 37.50 ms │ 37.32 ms │ no change │
│ QQuery 3 │ 31.02 ms │ 31.03 ms │ no change │
│ QQuery 4 │ 221.19 ms │ 221.65 ms │ no change │
│ QQuery 5 │ 269.59 ms │ 269.37 ms │ no change │
│ QQuery 6 │ 1.30 ms │ 1.28 ms │ no change │
│ QQuery 7 │ 12.86 ms │ 13.30 ms │ no change │
│ QQuery 8 │ 326.34 ms │ 329.13 ms │ no change │
│ QQuery 9 │ 451.18 ms │ 450.49 ms │ no change │
│ QQuery 10 │ 68.53 ms │ 69.50 ms │ no change │
│ QQuery 11 │ 79.99 ms │ 80.01 ms │ no change │
│ QQuery 12 │ 265.08 ms │ 266.12 ms │ no change │
│ QQuery 13 │ 956.19 ms │ 957.06 ms │ no change │
│ QQuery 14 │ 280.88 ms │ 279.41 ms │ no change │
│ QQuery 15 │ 261.79 ms │ 262.36 ms │ no change │
│ QQuery 16 │ 1200.53 ms │ 1218.74 ms │ no change │
│ QQuery 17 │ 928.57 ms │ 904.87 ms │ no change │
│ QQuery 18 │ 2434.58 ms │ 2465.65 ms │ no change │
│ QQuery 19 │ 27.37 ms │ 27.64 ms │ no change │
│ QQuery 20 │ 515.87 ms │ 517.77 ms │ no change │
│ QQuery 21 │ 513.02 ms │ 513.55 ms │ no change │
│ QQuery 22 │ 975.75 ms │ 976.75 ms │ no change │
│ QQuery 23 │ 2960.84 ms │ 2978.08 ms │ no change │
│ QQuery 24 │ 40.53 ms │ 40.86 ms │ no change │
│ QQuery 25 │ 110.21 ms │ 109.47 ms │ no change │
│ QQuery 26 │ 40.94 ms │ 41.05 ms │ no change │
│ QQuery 27 │ 512.39 ms │ 512.67 ms │ no change │
│ QQuery 28 │ 2904.32 ms │ 2922.14 ms │ no change │
│ QQuery 29 │ 41.69 ms │ 41.44 ms │ no change │
│ QQuery 30 │ 302.95 ms │ 298.48 ms │ no change │
│ QQuery 31 │ 279.90 ms │ 277.81 ms │ no change │
│ QQuery 32 │ 3225.83 ms │ 3228.48 ms │ no change │
│ QQuery 33 │ 2564.89 ms │ 2541.93 ms │ no change │
│ QQuery 34 │ 2536.30 ms │ 2587.22 ms │ no change │
│ QQuery 35 │ 275.84 ms │ 272.24 ms │ no change │
│ QQuery 36 │ 65.71 ms │ 67.01 ms │ no change │
│ QQuery 37 │ 35.33 ms │ 35.30 ms │ no change │
│ QQuery 38 │ 39.73 ms │ 40.79 ms │ no change │
│ QQuery 39 │ 135.45 ms │ 131.43 ms │ no change │
│ QQuery 40 │ 13.92 ms │ 14.14 ms │ no change │
│ QQuery 41 │ 13.51 ms │ 13.17 ms │ no change │
│ QQuery 42 │ 12.99 ms │ 12.92 ms │ no change │
└───────────┴────────────┴───────────────────────────────────────────────┴───────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary ┃ ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD) │ 25985.29ms │
│ Total Time (claude_single-distinct-to-groupby-allow-count) │ 26072.93ms │
│ Average Time (HEAD) │ 604.31ms │
│ Average Time (claude_single-distinct-to-groupby-allow-count) │ 606.35ms │
│ Queries Faster │ 0 │
│ Queries Slower │ 0 │
│ Queries with No Change │ 43 │
│ Queries with Failure │ 0 │
└──────────────────────────────────────────────────────────────┴────────────┘
Distribution per query (min / mean ±stddev / max):
Comparing HEAD and claude_single-distinct-to-groupby-allow-count
--------------------
Benchmark clickbench_partitioned.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query ┃ HEAD ┃ claude_single-distinct-to-groupby-allow-count ┃ Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 0 │ 1.22 / 3.91 ±5.33 / 14.58 ms │ 1.25 / 3.97 ±5.37 / 14.71 ms │ no change │
│ QQuery 1 │ 11.68 / 11.94 ±0.14 / 12.07 ms │ 12.03 / 12.32 ±0.22 / 12.69 ms │ no change │
│ QQuery 2 │ 37.50 / 37.77 ±0.24 / 38.17 ms │ 37.32 / 37.75 ±0.63 / 38.98 ms │ no change │
│ QQuery 3 │ 31.02 / 31.53 ±0.73 / 32.98 ms │ 31.03 / 31.31 ±0.28 / 31.77 ms │ no change │
│ QQuery 4 │ 221.19 / 224.04 ±2.27 / 227.93 ms │ 221.65 / 223.46 ±1.17 / 224.90 ms │ no change │
│ QQuery 5 │ 269.59 / 271.49 ±1.18 / 273.20 ms │ 269.37 / 272.36 ±1.94 / 274.88 ms │ no change │
│ QQuery 6 │ 1.30 / 1.44 ±0.22 / 1.87 ms │ 1.28 / 1.43 ±0.21 / 1.84 ms │ no change │
│ QQuery 7 │ 12.86 / 13.11 ±0.17 / 13.37 ms │ 13.30 / 13.37 ±0.07 / 13.46 ms │ no change │
│ QQuery 8 │ 326.34 / 332.09 ±4.45 / 339.84 ms │ 329.13 / 333.91 ±3.92 / 339.24 ms │ no change │
│ QQuery 9 │ 451.18 / 457.68 ±4.16 / 463.55 ms │ 450.49 / 455.52 ±3.60 / 460.05 ms │ no change │
│ QQuery 10 │ 68.53 / 71.31 ±4.52 / 80.34 ms │ 69.50 / 72.23 ±4.12 / 80.40 ms │ no change │
│ QQuery 11 │ 79.99 / 80.58 ±0.31 / 80.85 ms │ 80.01 / 81.13 ±0.76 / 82.19 ms │ no change │
│ QQuery 12 │ 265.08 / 269.64 ±3.57 / 275.49 ms │ 266.12 / 272.46 ±7.18 / 283.77 ms │ no change │
│ QQuery 13 │ 956.19 / 969.53 ±10.63 / 984.85 ms │ 957.06 / 976.17 ±12.86 / 996.44 ms │ no change │
│ QQuery 14 │ 280.88 / 283.62 ±3.21 / 289.84 ms │ 279.41 / 283.63 ±2.29 / 285.99 ms │ no change │
│ QQuery 15 │ 261.79 / 269.84 ±5.48 / 275.99 ms │ 262.36 / 267.68 ±4.38 / 274.46 ms │ no change │
│ QQuery 16 │ 1200.53 / 1219.68 ±18.42 / 1248.10 ms │ 1218.74 / 1247.92 ±21.47 / 1278.46 ms │ no change │
│ QQuery 17 │ 928.57 / 951.46 ±18.07 / 972.90 ms │ 904.87 / 914.21 ±7.34 / 922.41 ms │ no change │
│ QQuery 18 │ 2434.58 / 2476.29 ±36.51 / 2542.58 ms │ 2465.65 / 2486.71 ±29.59 / 2542.42 ms │ no change │
│ QQuery 19 │ 27.37 / 31.96 ±5.58 / 39.84 ms │ 27.64 / 28.73 ±1.31 / 30.65 ms │ +1.11x faster │
│ QQuery 20 │ 515.87 / 525.39 ±8.32 / 536.87 ms │ 517.77 / 521.42 ±3.40 / 527.72 ms │ no change │
│ QQuery 21 │ 513.02 / 522.00 ±7.04 / 532.59 ms │ 513.55 / 519.13 ±5.19 / 528.96 ms │ no change │
│ QQuery 22 │ 975.75 / 985.09 ±9.50 / 1003.37 ms │ 976.75 / 981.07 ±3.90 / 987.00 ms │ no change │
│ QQuery 23 │ 2960.84 / 2982.94 ±19.51 / 3011.99 ms │ 2978.08 / 3039.42 ±36.09 / 3085.69 ms │ no change │
│ QQuery 24 │ 40.53 / 49.60 ±17.64 / 84.87 ms │ 40.86 / 41.67 ±0.69 / 42.72 ms │ +1.19x faster │
│ QQuery 25 │ 110.21 / 116.62 ±10.09 / 136.27 ms │ 109.47 / 111.82 ±2.20 / 115.88 ms │ no change │
│ QQuery 26 │ 40.94 / 42.16 ±1.05 / 43.98 ms │ 41.05 / 41.25 ±0.21 / 41.61 ms │ no change │
│ QQuery 27 │ 512.39 / 515.08 ±3.87 / 522.74 ms │ 512.67 / 528.07 ±15.68 / 558.10 ms │ no change │
│ QQuery 28 │ 2904.32 / 2932.17 ±14.95 / 2943.95 ms │ 2922.14 / 2932.76 ±7.54 / 2944.08 ms │ no change │
│ QQuery 29 │ 41.69 / 57.91 ±24.17 / 104.36 ms │ 41.44 / 43.49 ±3.47 / 50.40 ms │ +1.33x faster │
│ QQuery 30 │ 302.95 / 314.95 ±12.52 / 335.35 ms │ 298.48 / 303.62 ±3.87 / 308.45 ms │ no change │
│ QQuery 31 │ 279.90 / 284.47 ±4.57 / 292.33 ms │ 277.81 / 296.29 ±26.77 / 349.15 ms │ no change │
│ QQuery 32 │ 3225.83 / 3272.59 ±31.22 / 3309.86 ms │ 3228.48 / 3278.90 ±50.47 / 3363.90 ms │ no change │
│ QQuery 33 │ 2564.89 / 2596.76 ±27.27 / 2641.47 ms │ 2541.93 / 2616.73 ±60.72 / 2709.78 ms │ no change │
│ QQuery 34 │ 2536.30 / 2623.74 ±51.75 / 2677.82 ms │ 2587.22 / 2628.46 ±36.65 / 2675.04 ms │ no change │
│ QQuery 35 │ 275.84 / 281.97 ±4.57 / 286.80 ms │ 272.24 / 285.56 ±10.47 / 298.87 ms │ no change │
│ QQuery 36 │ 65.71 / 71.08 ±4.26 / 76.24 ms │ 67.01 / 69.29 ±1.83 / 71.04 ms │ no change │
│ QQuery 37 │ 35.33 / 39.44 ±6.73 / 52.84 ms │ 35.30 / 47.78 ±21.84 / 91.36 ms │ 1.21x slower │
│ QQuery 38 │ 39.73 / 41.71 ±1.32 / 43.89 ms │ 40.79 / 43.15 ±1.45 / 45.00 ms │ no change │
│ QQuery 39 │ 135.45 / 154.19 ±16.86 / 184.94 ms │ 131.43 / 139.12 ±8.24 / 151.85 ms │ +1.11x faster │
│ QQuery 40 │ 13.92 / 15.47 ±2.43 / 20.29 ms │ 14.14 / 14.32 ±0.16 / 14.54 ms │ +1.08x faster │
│ QQuery 41 │ 13.51 / 14.03 ±0.26 / 14.23 ms │ 13.17 / 14.76 ±2.20 / 19.11 ms │ 1.05x slower │
│ QQuery 42 │ 12.99 / 14.65 ±2.60 / 19.82 ms │ 12.92 / 13.05 ±0.16 / 13.31 ms │ +1.12x faster │
└───────────┴───────────────────────────────────────┴───────────────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary ┃ ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD) │ 26462.94ms │
│ Total Time (claude_single-distinct-to-groupby-allow-count) │ 26527.38ms │
│ Average Time (HEAD) │ 615.42ms │
│ Average Time (claude_single-distinct-to-groupby-allow-count) │ 616.92ms │
│ Queries Faster │ 6 │
│ Queries Slower │ 2 │
│ Queries with No Change │ 35 │
│ Queries with Failure │ 0 │
└──────────────────────────────────────────────────────────────┴────────────┘

Memory Pool Peaks

Peak MemoryPool reservation per query — what DataFusion's accounting believes it reserved. Recorded only when the benchmark runs with DATAFUSION_RUNTIME_MEMORY_LIMIT set.

Base: 16ace4f (merge-base) | Changed: claude/single-distinct-to-groupby-allow-count

clickbench_partitioned

QueryBaseChangedChange
Query 00 B0 B0.0%
Query 1104 B104 B+0.0%
Query 2936 B936 B+0.0%
Query 3312 B312 B+0.0%
Query 4754.3 MiB754.9 MiB+0.1%
Query 51.2 GiB1.2 GiB-2.7%
Query 60 B0 B0.0%
Query 750.2 MiB40.3 MiB-19.7%
Query 8869.2 MiB892.2 MiB+2.6%
Query 9593.7 MiB593.9 MiB+0.0%
Query 10111.9 MiB112.8 MiB+0.7%
Query 11121.7 MiB118.7 MiB-2.5%
Query 121.4 GiB1.3 GiB-1.7%
Query 131.0 GiB1.0 GiB+1.7%
Query 141.3 GiB1.3 GiB-0.0%
Query 151.1 GiB1.1 GiB+2.0%
Query 161.8 GiB2.0 GiB+11.1%
Query 171.8 GiB1.7 GiB-2.5%
Query 182.1 GiB1.9 GiB-6.3%
Query 190 B0 B0.0%
Query 20104 B104 B+0.0%
Query 213.3 MiB3.3 MiB+0.6%
Query 222.5 MiB3.0 MiB+22.0%
Query 2333.2 MiB24.5 MiB-26.1%
Query 2459.5 MiB58.5 MiB-1.7%
Query 25173.3 MiB174.3 MiB+0.6%
Query 2660.8 MiB61.3 MiB+0.9%
Query 272.2 MiB2.4 MiB+10.0%
Query 281.5 GiB1.5 GiB-4.7%
Query 29624 B624 B+0.0%
Query 30680.8 MiB671.1 MiB-1.4%
Query 311.5 GiB1.4 GiB-3.2%
Query 32928.1 MiB929.0 MiB+0.1%
Query 332.1 GiB2.1 GiB-1.3%
Query 342.2 GiB2.1 GiB-1.5%
Query 35602.9 MiB624.7 MiB+3.6%
Query 36121.1 MiB113.6 MiB-6.2%
Query 377.4 MiB6.9 MiB-7.2%
Query 385.2 MiB5.7 MiB+9.1%
Query 39297.8 MiB298.1 MiB+0.1%
Query 402.0 MiB2.0 MiB-1.1%
Query 413.1 MiB3.1 MiB+0.0%
Query 421.7 MiB1.6 MiB-2.3%

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.

BenchmarkSideMax pool peakPeak RSSGapRSS / pool
clickbench_partitionedbase (16ace4f (merge-base))2.2 GiB8.6 GiB6.4 GiB4.0×
clickbench_partitionedchanged (claude/single-distinct-to-groupby-allow-count)2.1 GiB9.3 GiB7.2 GiB4.4×
Resource Usage

clickbench_partitioned — base (merge-base)

MetricValue
Wall time135.0s
Peak memory8.6 GiB
Avg memory5.1 GiB
CPU user1342.2s
CPU sys127.6s
Peak spill0 B

clickbench_partitioned — branch

MetricValue
Wall time135.0s
Peak memory9.3 GiB
Avg memory5.5 GiB
CPU user1345.1s
CPU sys129.3s
Peak spill0 B

File an issue against this benchmark runner

@adriangb

Copy link
Copy Markdown
ContributorAuthor

Thanks for the review @kosiew 🙏🏻

I addressed your feedback but in poking around I decided this needs one more pass. I'll bother you with another request for review once complete.

@adriangb

Copy link
Copy Markdown
ContributorAuthor

Okay @kosiew this is ready now. Could you take another look?

@adriangb
adriangb requested a review from kosiewSeptember 8, 2026 14:23
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

functionsChanges to functions implementationlogical-exprLogical plan and expressionsoptimizerOptimizer rulessqllogictestSQL Logic Tests (.slt)substraitChanges to the substrait crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@adriangb@codecov-commenter@adriangbot@kosiew