[ 
https://issues.apache.org/jira/browse/SPARK-56908?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Gengliang Wang updated SPARK-56908:
-----------------------------------
    Summary: Reduce the size of code generated by whole-stage codegen  (was: 
Reduce generated Java size in whole-stage codegen)

> Reduce the size of code generated by whole-stage codegen
> --------------------------------------------------------
>
>                 Key: SPARK-56908
>                 URL: https://issues.apache.org/jira/browse/SPARK-56908
>             Project: Spark
>          Issue Type: Umbrella
>          Components: SQL
>    Affects Versions: 4.3.0
>            Reporter: Gengliang Wang
>            Priority: Major
>              Labels: pull-request-available
>
> Whole-stage codegen generates a fresh Java class per stage. Across many 
> operators the generated source contains (a) boilerplate that is 
> type-independent across stages and can be deduplicated into static Java 
> helpers, and (b) branches or variables that are statically dead at codegen 
> time but emitted anyway.
> These patterns cost us in three places:
> - JVM 64KB method-size and constant-pool limits, which force interpreted 
> fallback on deep query plans.
> - Janino compile time per stage.
> - JIT compile work (each stage class has its own bodies).
> This umbrella tracks small, behavior-preserving cleanups across the generated 
> Java to address these issues. Each subtask is independently PR-able; behavior 
> is preserved end-to-end and verified by the relevant operator's existing test 
> suite with {{spark.sql.codegen.wholeStage}} forced both on and off.
> h3. Scope guidance (when is a dead-branch / simplification subtask worth it?)
> Not every statically-dead branch is worth eliminating. We measured the payoff 
> on real generated code (TPC-DS whole-stage codegen): Janino constant-folds 
> {{if (true)}} / {{if (false)}}, so skipping such a branch produces *no 
> bytecode change* and no JIT/runtime benefit -- only smaller source. Janino 
> compile time is ~linear in source (~0.36 ms/KB), and even the most frequent 
> source-only patterns we measured ({{if (true)}} x445, a dead null-write 
> branch x358 -- together only ~0.4% of generated source) saved compile time 
> below the run-to-run noise floor (~0.7%).
> Therefore a subtask that adds branching / complexity to the codegen logic to 
> skip a dead branch is only justified when:
> - *(b) it removes more than source text -- this is the real bar.* For example 
> SPARK-57198: skipping the divide-by-zero guard for a non-zero literal also 
> stops registering the unreachable {{errCtx}} entry in the {{references[]}} / 
> constant pool, which Janino cannot fold away (unlike the {{if (false)}} text 
> itself). Likewise SPARK-57199 moved the {{AGGREGATE_OUT_OF_MEMORY}} string + 
> map constructor out of all 142 generated aggregate classes' constant pools 
> into one compiled method. Keeping a large method under the 64KB / huge-method 
> limit also qualifies.
> - *(a) raw frequency rarely suffices on its own.* As measured above, even 
> patterns occurring 358-445 times stayed below the compile-time noise floor 
> with no bytecode change. Frequency only matters when the dead code is a large 
> fraction of a _single_ method (approaching the 64KB limit), not merely 
> numerous across the corpus.
> Trivial, infrequent (and even frequent-but-source-only) dead-branch removals 
> add codegen-logic complexity for negligible benefit and should be dropped 
> rather than merged.
> h3. Measured cumulative impact (as of master, 2026-07)
> We now measure generated codegen size across all TPC-DS queries with a 
> dedicated, planning-only benchmark added under this umbrella (SPARK-57915): 
> {{org.apache.spark.sql.execution.benchmark.WholeStageCodegenSizeBenchmark}}. 
> It creates the TPC-DS tables empty (SF=100 stats injected so plan shapes 
> match production), plans every query to its {{executedPlan}} with no data or 
> execution, walks the {{WholeStageCodegenExec}} subtrees via 
> {{debug.codegenStringSeq}}, and reports grand totals: generated source size, 
> summed/max compiled method bytecode, generated inner-class count, 
> constant-pool entries, cold Janino compile time, and codegen fallbacks. Run 
> it with {{build/sbt "sql/Test/runMain 
> org.apache.spark.sql.execution.benchmark.WholeStageCodegenSizeBenchmark"}}.
> Comparing {{branch-4.2}} (the 4.2 cut on 2026-05-01, before the bulk of this 
> umbrella's subtasks) against current {{master}} (which carries ~40 
> SPARK-56908 subtasks merged since) over all 135 measured TPC-DS queries:
> || metric || branch-4.2 || master || delta ||
> | source code size (chars) | 24,190,570 | 21,845,997 | -9.7% |
> | max method bytecode, summed over stages | 951,309 | 841,185 | -11.6% |
> | max method bytecode, single largest method | 6,527 | 4,962 | -24.0% |
> | generated inner classes | 786 | 258 | -67.2% |
> | constant pool, summed over stages | 454,006 | 432,875 | -4.7% |
> | Janino compile time (ms) | 10,784 | 10,362 | -3.9% |
> | codegen fallbacks | 0 | 0 | unchanged |
> The largest wins are in the metrics that actually gate execution and cost: 
> the single largest generated method shrank ~24% (the metric behind the 
> HotSpot 8KB JIT-compilation threshold and the 64KB method limit), and 
> generated inner classes dropped ~67% (fewer classes for Janino to compile, 
> load, and verify, and less metaspace per query per executor). No query 
> regressed on any metric and codegen fallbacks stayed at zero, confirming the 
> changes are behavior-preserving.
> This is the cumulative effect of the whole umbrella's work over that window 
> (dead-check elimination such as SPARK-57198/SPARK-57201, static-helper 
> extraction across many expressions, and the recent fast-hash-map / 
> UnsafeRowWriter / ColumnarToRow / Coalesce reductions), not of any single 
> subtask; each subtask's own before/after numbers are in its linked PR.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

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

Reply via email to