txwyy123 opened a new pull request, #59841: URL: https://github.com/apache/spark/pull/59841
### What changes were proposed in this pull request? For SortMergeJoin with many output columns (80+), `processNext()` contains all column extraction code inline — reading every column from both the streamed and buffered rows. This pushes `processNext()` past the 8,000-byte JVM bytecode limit where HotSpot C2 refuses to JIT-compile, causing the method to run interpreted at ~4x penalty. This PR adds **field-based extraction** for both sides of a SortMergeJoin: 1. **`genOneSideJoinVarsAsFields`** (buffered side in `JoinCodegenSupport`): stores extracted column values in class-level mutable fields (`ctx.addMutableState`) instead of local variables, then uses `ctx.splitExpressions` to batch extraction code into small helper methods that take `InternalRow bufferedRow` as a parameter. 2. **`createStreamedVarsAsFields`** (streamed side in `SortMergeJoinExec`): same pattern but with **no arguments** to `splitExpressions`, since `streamedRow` is already a class field from `genScanner`. The helper methods reference it directly. For a 40+40 column join, `processNext` shrinks from 10,221 to 5,766 source chars (**43% reduction**), with column access operations dropping from 204 to 1 inside `processNext`: | Metric | Before | After | |---|---|---| | processNext lines | 313 | 197 | | processNext source chars | 10,221 | 5,766 | | Column access ops in processNext | 204 | 1 | | Extraction helper methods | 0 | 6 | ### Why are the changes needed? The JVM's C2 JIT compiler refuses to compile methods with ≥ 8,000 bytes of bytecode ([the 8K cliff](https://vecbricks.github.io/the-8000-byte-cliff/)). When `processNext()` exceeds this limit on wide joins, it silently falls back to the bytecode interpreter. Spark's `maxFields=100` default masks this by disabling codegen entirely for wide schemas, but users who raise `maxFields` to enable codegen on wider queries hit the cliff. Distributed EMR benchmarks (10× r5.4xlarge, 5M rows/side, Spark 3.5.2) confirmed the problem: | Output columns | Codegen ON | Codegen OFF | Delta | |---|---|---|---| | 42 | 2,715 ms | 2,750 ms | **+1%** ✅ processNext < 8K | | 62 | 3,403 ms | 3,460 ms | **+2%** ✅ processNext < 8K | | 102 | 5,173 ms | 4,896 ms | **-5%** ❌ processNext > 8K | | 122 | 5,948 ms | 5,487 ms | **-8%** ❌ interpreted codegen WORSE | At 100+ output columns, codegen ON is **5–8% slower** than codegen OFF because interpreted codegen is worse than the Volcano iterator model (the pre-compiled `Row.getInt(i)` accessors are JIT'd, while the dynamically-generated Janino bytecode is not). ### Does this PR introduce _any_ user-facing change? No. This is a performance optimization. Query results are unchanged. ### How was this patch tested? - New tests in `WholeStageCodegenSuite` for wide SMJ (40+40 cols) and BHJ (40+40 cols) with `maxFields=200` - Full existing test suites pass: WholeStageCodegenSuite (99), JoinSuite (59), OuterJoinSuite (96), InnerJoinSuite + ExistenceJoinSuite (514) - `WideJoinBenchmark` measuring `processNext` method size before/after - Distributed EMR benchmark (10× r5.4xlarge, Spark 3.5.2) measuring wall-clock query time across 7 column widths ### Was this patch authored or co-authored using generative AI tooling? Yes. -- 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]
