gengliangwang opened a new pull request, #57059:
URL: https://github.com/apache/spark/pull/57059

   ### What changes were proposed in this pull request?
   
   When `HashAggregateExec` cannot allocate an aggregation buffer from the 
in-memory hash map, its
   generated code spills the map to disk: the first spill destructs the map 
into a new
   `UnsafeKVExternalSorter`, and later spills merge the map into the existing 
sorter. This
   type-independent branch is re-emitted into every HashAggregateExec 
whole-stage-codegen stage.
   
   This PR extracts it into a shared helper
   `HashAggregateExec.spillHashMapToSorter(hashMap, sorter): 
UnsafeKVExternalSorter` and calls it from
   the generated code. Returning the sorter lets the generated code keep it in 
a single mutable field.
   
   Before (generated code, per HashAggregateExec stage):
   ```java
   if (hashAgg_unsafeRowAggBuffer_0 == null) {
     if (hashAgg_sorter_0 == null) {
       hashAgg_sorter_0 = hashAgg_hashMap_0.destructAndCreateExternalSorter();
     } else {
       
hashAgg_sorter_0.merge(hashAgg_hashMap_0.destructAndCreateExternalSorter());
     }
     ...
   ```
   
   After:
   ```java
   if (hashAgg_unsafeRowAggBuffer_0 == null) {
     hashAgg_sorter_0 = 
org.apache.spark.sql.execution.aggregate.HashAggregateExec
       .spillHashMapToSorter(hashAgg_hashMap_0, hashAgg_sorter_0);
     ...
   ```
   
   This follows the same "extract type-independent generated machinery into a 
compiled helper" pattern
   as SPARK-57909 (`ColumnarToRowExec.advanceBatch`), and mirrors the existing
   `HashAggregateExec.finishAggregate` helper that already backs the 
aggregate's output path. The helper
   is a static method (it needs nothing from the plan instance), so -- unlike 
routing through the
   `references[]` plan object -- it adds no per-stage constant-pool entry.
   
   Only the fully type-independent spill-and-create block is moved. The 
surrounding logic that is
   interleaved with the test-only controlled-fallback counter (`resetCounter`) 
and the retry lookup
   (which uses the schema-specific key/hash variables) is left inline unchanged.
   
   Measured with `WholeStageCodegenSizeBenchmark` (added under SPARK-57915; 
plans all 135 TPC-DS
   queries, 562 of the 2247 whole-stage-codegen stages carry this block), 
current `master` vs. this
   change:
   
   | metric | master | this PR | delta |
   | --- | --- | --- | --- |
   | constant pool, summed over stages | 432,881 | 431,233 | -0.4% |
   | source code size (chars) | 21,846,001 | 21,794,526 | -0.2% |
   | max method bytecode, summed over stages | 841,185 | 841,048 | ~unchanged |
   | inner classes | 258 | 258 | unchanged |
   | codegen fallbacks | 0 | 0 | unchanged |
   
   The block lives in the aggregate's `doAggregateWithKeys` helper method 
(rarely a stage's single
   largest method), so `max method bytecode (sum)` barely moves; the reduction 
shows up in the
   constant pool, since the `destructAndCreateExternalSorter` / `merge` method 
references are now
   emitted once instead of per stage.
   
   ### Why are the changes needed?
   
   This is part of the umbrella SPARK-56908 (reduce the size of code generated 
by whole-stage codegen).
   The spill branch is real bytecode and constant-pool method-references (the 
`destructAndCreateExternalSorter`
   and `merge` calls) that Janino cannot fold away; collapsing it to a single 
helper call compiles it
   once per JVM instead of re-emitting it into every HashAggregateExec stage. 
Planning all 135 TPC-DS
   queries produces 562 HashAggregateExec whole-stage-codegen stages that carry 
this block.
   
   ### Does this PR introduce _any_ user-facing change?
   
   No. The helper performs exactly the same spill/merge operations in the same 
order as the previous
   inline code; this is a pure code-organization change with no behavioral 
difference.
   
   ### How was this patch tested?
   
   Existing tests, run with whole-stage codegen both on and off:
   - `HashAggregationQueryWithControlledFallbackSuite`, which forces the hash 
map to spill at controlled
     points (`fallbackStartsAt` = 1, 2, 3) and therefore exercises both the 
first-spill (destruct) and
     later-spill (merge) branches of the extracted helper, and
   - `DataFrameAggregateSuite` / `AggregationQuerySuite` for general aggregate 
correctness.
   
   No behavior changes, so no new tests are added; the extracted branch is 
directly covered by the
   controlled-fallback suite.
   
   ### Was this patch authored or co-authored using generative AI tooling?
   
   Generated-by: Claude Code (Opus 4.8)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to