MaxGekk commented on PR #59069:
URL: https://github.com/apache/spark/pull/59069#issuecomment-5849776366
@dongjoon-hyun thank you for the careful trace - both correctness findings
were real. All items are addressed in 8750bdd8107, plus a commit with the
benchmark results.
**1. `With` and `INPUT_ROW`.** A `CommonExpressionRef` now sets `needsRow`
when `INPUT_ROW` is set, since `CommonExprSlots` may make its definition a
method that takes the row. New test: "a common expression in a branch under a
keyed aggregate takes the buffer", your query shape, asserting the stage splits
and matches the non-WSCG answer.
**2. Discarded `computeCommonExpr_N`.** Functions are now recorded as
discardable in `addNewFunctionToClass`, for the whole non-split pass, so
`computeCommonExpr_N` goes with the `caseWhen_*` methods it calls; an inner
pass's functions join the enclosing pass's. That replaced the name snapshot and
diff. New test: "functions added in a discarded pass of subexpression
elimination go", your `ProjectExec` example.
**3. Binary compatibility.** The six-argument
`splitExpressionsWithCurrentInputs` is back unchanged, and the split under WSCG
is a separate overload taking `sources: Seq[Seq[Expression]]` with no default
values. The other 22 callers use the old method, so they also no longer pay the
block regexes under WSCG (your first minor item).
**4. Tests for the arms.** Added a keyed `max_by`/`min_by` test (the
`needsRow` path through the buffer), the `With` and discarded-pass tests above,
and a refusal test: a CASE WHEN whose methods would need more parameter slots
than `validParamLength` stays in one piece.
**5. Join test.** It now asserts that the condition was split, in all three
queries, and the wrong comment about the streamed side is gone.
**6. ROLLUP / compacted slots.** You're right that no test reached the slot
branch, so I removed it: a compacted `mutableStateArray_0[3]` is not a name,
and the split is refused for it.
**7. `stageCodes`.** Removed; the helpers use `genCode(df)`, so they fail
when no stage is generated.
**Minor:**
- *Union of all branch inputs:* each split method now takes only what its
own block reads, and a method grouping calls takes the union of what its calls
take. Test: "each split method takes only what its own branches read", with 90
distinct nullable `bigint` columns.
- *Small `CASE WHEN`s:* added `CaseWhenCodegenBenchmark`, 2 to 300 branches,
with the split, without it, and with WSCG off, 2M rows; results for JDK
17/21/25 on the GitHub runner (EPYC 9V74) are committed. Best times in ms,
split / not split:
| branches | JDK 17 | JDK 21 | JDK 25 |
|---|---|---|---|
| 2 | 88 / 69 | 74 / 83 | 78 / 65 |
| 16 | 69 / 69 | 67 / 65 | 64 / 62 |
| 32 | 92 / 88 | 75 / 88 | 71 / 85 |
| 64 | 116 / 103 | 126 / 104 | 120 / 101 |
| 128 | 190 / 5395 | 287 / 4094 | 225 / 4793 |
| 300 | 450 / 11866 | 462 / 10791 | 458 / 11254 |
Up to 32 branches the two are within the noise - at 2 branches the
generated code is identical (280 bytes either way) and the times still differ
by up to 25%. At 64 branches the split is 13-21% slower on all three JDKs.
`-XX:+LogCompilation` on JDK 25 shows why: unsplit, C2 compiles
`project_doConsume_0` (6049 bytes) as one unit, under HotSpot's 8000-byte
limit. Split, `doConsume` (627 bytes) calls 32 `caseWhen_*` methods of about
198 bytes in sequence; C2 inlines the first 13 and then stops - 11 fail with
`size > DesiredMethodLimit` (the inlining budget is spent) and the last 8 with
`too big` (their call sites are reached by fewer rows, so they are not hot) -
so a row whose value matches a later branch makes real calls. From 128 branches
the unsplit method passes the 8000-byte limit and is not JIT-compiled, and the
split is 14-26x faster. So the split trades up to ~20% in the band where the
unsplit method still compiles for an order of magnitude past it - the same
trade the non
-WSCG split already makes.
- *`wholeStageSplitArguments` vs `CommonExprSlots.methodArgs`:* agreed that
they should be one walker. Item 1 closes the `INPUT_ROW` gap, and the scaladoc
lists which refusals they share and which are its own. Merging them,
parameterised by how a subexpression state is handled, is a follow-up with its
own JIRA - it changes `With`'s method arguments too, and I'd rather not widen
this PR to that.
- *`Cast` arm:* `Cast` now exposes the pass-through test
(`generatesChildCode`) and both places use it.
- *`LambdaVariable`:* the refusal now says the value is a field that the
loop assigns; the refusal itself stays, because no test reaches such a split.
- *`CodegenFallback` arm:* removed.
Could you take another look when you have a chance?
--
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]