[
https://issues.apache.org/jira/browse/SPARK-58501?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Wenchen Fan reassigned SPARK-58501:
-----------------------------------
Assignee: James Xu
> [SQL] Eliminate redundant subexpression evaluation across Expand branches in
> whole-stage codegen
> ------------------------------------------------------------------------------------------------
>
> Key: SPARK-58501
> URL: https://issues.apache.org/jira/browse/SPARK-58501
> Project: Spark
> Issue Type: Improvement
> Components: SQL
> Affects Versions: 4.3.0
> Reporter: James Xu
> Assignee: James Xu
> Priority: Major
> Labels: pull-request-available
>
> h3. Problem:
> Traffic/BI rollup queries stack many conditional aggregates whose
> conditions
> share one expensive subexpression. A typical "N-day active users" dashboard
> query computes retention over many windows in a single pass:
> {code:java}
> SELECT
> COUNT(DISTINCT IF(datediff(date '2026-01-01',
> from_unixtime(unix_timestamp(ts, 'yyyy-MM-dd HH:mm:ss'))) <= 1,
> uid, NULL)) AS uv_1d,
> COUNT(DISTINCT IF(datediff(date '2026-01-01',
> from_unixtime(unix_timestamp(ts, 'yyyy-MM-dd HH:mm:ss'))) <= 7,
> uid, NULL)) AS uv_7d,
> SUM(IF(datediff(date '2026-01-01',
> from_unixtime(unix_timestamp(ts, 'yyyy-MM-dd HH:mm:ss'))) <= 1,
> 1, 0)) AS pv_1d,
> -- ... 15 more conditional aggregates over longer windows
> FROM traffic{code}
> RewriteDistinctAggregates gives each distinct group its own Expand branch
> with the condition expression verbatim, so the shared datetime
> subexpression
> (unix_timestamp parse + from_unixtime format + datediff) appears in every
> branch. In whole-stage codegen it is compiled into each branch body and
> re-evaluated once per branch per input row, even though every branch of an
> Expand consumes the same input row.
> h3. Root Cause:
> Two facts combine to produce the redundant work:
> - RewriteDistinctAggregates groups distinct aggregates by their unfoldable
> child sets. Each unique IF(cond_i, col, NULL) child forms its own group,
> so N conditions produce N Expand branches, and the branch projections
> carry the full condition expressions (not attributes).
> - Expand whole-stage codegen never participated in subexpression
> elimination. Each branch body is generated independently, so a subtree
> shared by B branches is emitted B times and evaluated B times per input
> row. In the example above the shared subexpression is evaluated 18 times
> per input row (9 distinct-aggregate conditions + 9 regular-aggregate
> conditions across 10 branches).
> Grouping keys do not suffer from this: complex grouping expressions are
> pulled into a Project below the Expand and referenced as attributes. Only
> branch-varying expressions such as aggregate conditions are affected.
> h3. Solution:
> During whole-stage codegen, analyze all branch expressions of an Expand
> together: any subtree shared across branches (or repeated within a branch)
> is evaluated once per input row, before the branch loop, and each branch
> references the cached value. Hoisting is semantics-preserving because every
> branch consumes the same input row.
> - Applies automatically wherever Expand branches carry repeated
> non-trivial expressions, most notably multi-conditional DISTINCT
> aggregate queries produced by RewriteDistinctAggregates.
> - Interoperates with the large-Expand safeguard that splits branch bodies
> into separate generated methods for JVM method-size limits: the cached
> values are passed into the split methods as parameters.
> - No new configuration. The optimization honors the existing subexpression
> elimination setting and is active whenever whole-stage codegen is.
> - Evaluation results are unchanged; only the number of evaluations per
> input row changes.
> h3. Expected Impact:
> Measured with a new ExpandBenchmark case modeling the rollup above (5M
> rows, 3 iterations, Apple M4 Pro, JDK 17):
> - Shared subexpression evaluations per input row: 18 -> 1
> - Average runtime: 82485 ms -> 51457 ms (15701 -> 9756 ns/row), 1.6X faster
> The benefit grows with the number of branches and with the cost of the
> shared subexpression (datetime parsing, regex, JSON extraction). Queries
> whose Expand branches share no non-trivial subexpressions are unaffected.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]