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

   ### What changes were proposed in this pull request?
   
   Under whole-stage codegen, `ProjectExec` generates all its outputs into one 
method. This PR lets the SPARK-33301 gate split a projection's outputs, each 
into a method of its own, when the method they land in is past the JIT limit.
   
   - `CodegenContext.generateOutputsWithWholeStageSplit`, which 
`ProjectExec.doConsume` now calls instead of `exprs.map(_.genCode(ctx))`, 
follows `wholeStageSplit` as `splitExpressionsWithSources` does: off, the 
outputs are generated as before; under `Record`, they are generated as before 
and marked; under `Split`, when the gate chose this projection, each output 
gets a method.
   - A split output's method stores its value in fields, as subexpression 
elimination's split functions do, and the output's code becomes the call. The 
operator consuming the projection still evaluates each output where it reads 
it, so no output is evaluated earlier than without the split. Splitting outputs 
in blocks would: under ANSI mode, for example, a block evaluated ahead of a 
filter above the projection raises an error for a row the filter drops.
   - The method takes the locals the output's code reads, found by 
`collectInputs` under a new policy, `InputPolicy.wholeStageOutput`: 
`wholeStageSplit` with `hoistDeferredInputs`. The inputs the output reads that 
are not evaluated yet are evaluated just ahead of its call instead of inside 
its code. Only that output reads them, since `ProjectExec` defers only the 
inputs one output reads (`usedInputs`).
   - An output that only passes on an input or a literal stays in place, and so 
does one whose inputs no method can take. A projection with fewer than two 
outputs to move is left to the split within its expression (CASE WHEN today, 
SPARK-60047 for the others), since moving one output just moves the large 
method.
   - `nextWholeStageSplitKey` and `wholeStageSplitArguments` are refactored so 
that the CASE WHEN split and this one share them.
   
   A stage whose methods the JIT compiles whole keeps its code byte for byte, 
as with the CASE WHEN split.
   
   ### Why are the changes needed?
   
   A projection of a few dozen ordinary expressions puts the stage's method 
past HotSpot's 8000-byte limit for JIT-compiling a method, although no single 
output is large. Spark falls back from whole-stage codegen only past 
`spark.sql.codegen.hugeMethodLimit` (64KB), so the stage keeps whole-stage 
codegen and runs interpreted. Measured on master (local, JDK 17), 300,000 rows, 
each column `concat_ws('-', upper(s), lower(s), cast(id + i as string), 
substring(s, i, 3), cast(id * i % 7 as string))`:
   
   | Columns | Whole-stage codegen on | Whole-stage codegen off |
   |---|---:|---:|
   | 50 | 7.7 s | 1.7 s |
   | 90 | 13.3 s | 2.9 s |
   
   At 50 columns the largest method of the stage is 13,187 bytes. Numbers with 
this change will follow.
   
   ### Does this PR introduce _any_ user-facing change?
   
   No, other than the performance of stages with wide projections.
   
   ### How was this patch tested?
   
   New tests in `WholeStageCodegenSuite`:
   - a 50-output projection gets a method per output and every method of the 
stage is under 8000 bytes; with `spark.sql.codegen.wholeStage.splitExpressions` 
off it is past the limit;
   - a projection the JIT compiles whole keeps the code it has with the split 
off;
   - an output that only passes on a column or a literal stays in place;
   - under ANSI mode, a split output is not evaluated for a row a filter above 
a nondeterministic projection drops (`10 DIV (id - 5)`);
   - each output's method takes the column it reads, and a common 
subexpression's locals;
   - results match those without whole-stage codegen under a range of consumers 
(end of the stage, aggregate, broadcast and sort-merge join, rollup, generator, 
sort with limit), with subexpression elimination on and off.
   
   `SPARK-33301: a split that turns no block into a method is not compiled` is 
reshaped: in its query, the output split makes the CASE WHEN splittable (its 
outputs' inputs are evaluated ahead of their calls), so the CASE WHEN and the 
common subexpressions are now one output, which keeps the case it tests.
   
   ### Was this patch authored or co-authored using generative AI tooling?
   
   Yes. Co-authored-by: Claude Code (Claude Opus 5.5)
   


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