James Xu created SPARK-58501:
--------------------------------

             Summary: [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


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]

Reply via email to