[SPARK-32750][SQL] Support whole-stage codegen for SortAggregate with grouping keys#57153
[SPARK-32750][SQL] Support whole-stage codegen for SortAggregate with grouping keys#57153ulysses-you wants to merge 2 commits into
Conversation
… grouping keys Add whole-stage code-gen support for SortAggregateExec when it has grouping keys. Previously only the no-grouping-keys path was code-gen'd; with keys it fell back to the interpreted SortBasedAggregationIterator. A new internal config spark.sql.codegen.aggregate.sortAggregate.withKeys.enabled (default true) gates this path and takes effect only when spark.sql.codegen.aggregate.sortAggregate.enabled is enabled. Co-Authored-By: Claude <noreply@anthropic.com>
|
.cc @viirya @dongjoon-hyun @cloud-fan if you have time to take a look, thank you! |
|
Long time no see. |
| val doAgg = ctx.freshName("doAggregateWithKeys") | ||
| val doAggFuncName = ctx.addNewFunction(doAgg, | ||
| s""" | ||
| |private void $doAgg() throws java.io.IOException { |
There was a problem hiding this comment.
The $doAgg wrapper here doesn't take an int partitionIndex parameter, unlike the no-keys path, HashAggregateExec, and SortExec. The child's produce emits bare partitionIndex references (e.g. addToSorter(partitionIndex) for a Sort child). When the outer generated class exceeds GENERATED_CLASS_SIZE_THRESHOLD (1MB) and addNewFunction spills $doAgg into a private class NestedClass, that bare reference resolves to the protected BufferedRowIterator.partitionIndex field, which the inner (non-subclass) class can't access, so Janino compilation throws IllegalAccessError. That's an Error, not NonFatal, so the codegen fallback in WholeStageCodegenExec won't catch it and the task fails. Suggest matching the other three wrappers: private void $doAgg(int partitionIndex), called as $doAggFuncName(partitionIndex).
| } | ||
|
|
||
| protected override def doProduceWithKeys(ctx: CodegenContext): String = { | ||
| throw new SparkUnsupportedOperationException("_LEGACY_ERROR_TEMP_3170") |
There was a problem hiding this comment.
This PR removes the only two throw sites for _LEGACY_ERROR_TEMP_3170 in SortAggregateExec, leaving the entry at error-conditions.json:11601 unreferenced. `
| } | ||
|
|
||
| test("SPARK-32750: SortAggregate code-gen with grouping keys") { | ||
| val data = spark.range(200).selectExpr( |
There was a problem hiding this comment.
supportCodegenWithKeys gates only on isBinaryStable, and Double/Float/Decimal are all binary-stable, so they take this new path. Group boundaries use raw UnsafeRow.equals, so float-key correctness rides on the planner's NormalizeFloatingNumbers folding -0.0/NaN first, but the new tests only cover long/string keys with no float grouping-key correctness assertion (decimal appears only in the benchmark). Suggest adding a float grouping-key case (with
| |$evaluateKeyVars | ||
| |${consume(ctx, resultVars)} | ||
| """.stripMargin | ||
| } else { |
There was a problem hiding this comment.
The third branch of generateResultFunctionForKeys (no aggregate functions, grouping-only) is codegen'd, but every new test carries at least one aggregate function, so this branch and its empty bufVars/reInitBufferCode path are uncovered. Suggest adding a groupBy("k").agg() or distinct case.
What changes were proposed in this pull request?
This PR adds whole-stage code-gen support for
SortAggregateExecwhen it has groupingkeys. Previously only the no-grouping-keys path was code-gen'd; with keys it fell back to
the interpreted
SortBasedAggregationIterator.The implementation:
AggregateCodegenSupportis refactored to share the aggregation-buffer creation(
createAggBufVars) and buffer-update (generateAggBufferUpdateCode) logic between theno-keys path and the new sort-based with-keys path.
SortAggregateExecimplementsdoProduceWithKeys/doConsumeWithKeys. Since the inputis sorted by the grouping keys, a group's result is emitted as soon as the next group
starts (detected by comparing the binary representation of the grouping key). The produce
loop is resumable across
processNextinvocations viashouldStop(), so a completedgroup's output row is not overwritten by the reused output buffer.
supportCodegen), because group boundaries are detected viaUnsafeRow.equals.spark.sql.codegen.aggregate.sortAggregate.withKeys.enabled(default true) gates this path; it takes effect only when
spark.sql.codegen.aggregate.sortAggregate.enabledis enabled.Why are the changes needed?
Sort aggregate with grouping keys was the only remaining aggregate path without whole-stage
code-gen, forcing it through the slower interpreted iterator.
SortAggregateBenchmarkshowsa consistent 1.2~1.3x speedup for grouped aggregates on the code-gen path.
Does this PR introduce any user-facing change?
No. This is an internal code-gen optimization; results are unchanged. The new config is
internal().How was this patch tested?
WholeStageCodegenSuitecovering: multiple/numeric/string/decimal groupingkeys, null keys and values, single-row groups, a single all-rows group, downstream limit
(resumable
shouldStoppath), empty input, single partition, split aggregate functions,FILTER clauses, HAVING-style filters, and the config gate. Each test asserts a code-gen'd
SortAggregateExecin the plan and checks the result matches the interpreted (code-gendisabled) result.
SortAggregateBenchmarkwith results committed for JDK 17/21/25.The generated code of a simple query:
select id , count(*) from t1 group by id:Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Code