Skip to content

perf: don't re-inline CSE'd expensive expressions in projection pushdown#23459

Open
fordN wants to merge 2 commits into
apache:mainfrom
fordN:perf/cse-projection-pushdown
Open

perf: don't re-inline CSE'd expensive expressions in projection pushdown#23459
fordN wants to merge 2 commits into
apache:mainfrom
fordN:perf/cse-projection-pushdown

Conversation

@fordN

@fordN fordN commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Follow-up to #23395 (the volatile correctness fix, now merged), extending the same projection-pushdown guard to the performance case.

Rationale for this change

The logical CSE pass extracts a repeated scalar-function call (e.g. power(a, 2)) into a single intermediate projection referenced by column, so it is evaluated once per row. For file sources that can absorb computed projections (e.g. Parquet), the physical projection-pushdown rule then merges that projection into the scan's DataSourceExec, re-inlining the expression at every reference site and re-evaluating it N times per row — undoing the deduplication.

This is the performance sibling of #23220 (which fixed the correctness case for volatile expressions). It reproduces on file scans (Parquet/CSV), not in-memory tables.

EXPLAIN, beforepower(a, 2) evaluated 3× per row:

DataSourceExec: projection=[power(a,2)+b as x, power(a,2)-b as y, power(a,2)*c as z]

EXPLAIN, afterpower(a, 2) evaluated once, kept in a ProjectionExec:

ProjectionExec: [__common_expr_1+b as x, __common_expr_1-b as y, __common_expr_1*c as z]
  DataSourceExec: projection=[power(a,2) as __common_expr_1, b, c]

What changes are included in this PR?

  • Extends the projection-pushdown guard in FileScanConfig::try_swapping_with_projection so it also declines the merge when it would duplicate a non-trivial expression. "Non-trivial" is decided by the existing expression-placement signal (KeepInPlace) — the same signal try_collapse_projection_chain uses — covering arithmetic, casts, and most scalar functions. Cheap leaf-pushable expressions (columns, get_field, input_file_name) still merge, so struct-field pushdown is unaffected. It reuses the volatile guard's multiplicity-aware reference counting, so an expression is blocked only when referenced more than once.
  • Adds the cse_projection_pushdown benchmark used to measure the change.

Are these changes tested?

Yes:

  • Unit tests in file_scan_config.rs: a non-trivial expr (arithmetic / scalar function) referenced ≥2 → blocked, once → allowed; a leaf-pushable scalar function (get_field) → allowed; volatile referenced ≥2 → blocked, once → allowed.
  • datafusion-sqllogictest suite passes. One existing plan in window.slt improves: a repeated c2 >= 2 comparison over a CSV scan is now computed once instead of being inlined twice into the DataSourceExec.
  • Benchmark A/B on cse_projection_pushdown (sample-size 50, 5s), with the no_repeated_exprs control confirming no change on unaffected queries:
Benchmark Change
repeated_power (power(a,2) ×3) −40%
repeated_nested_fn (ln(abs(a)) ×3) −38%
repeated_sqrt (sqrt(a) ×3) −37%
mixed_repeated_unique −35%
repeated_cheap_abs (abs(a) ×3) within noise
no_repeated_exprs (control) no change

The gain scales with expression cost. For a single-instruction function like abs, caching the value in a ProjectionExec versus recomputing it is a wash (run-to-run noise), since the extra plan node roughly offsets the recomputation it saves.

Reproduce with:

cargo bench -p datafusion --bench cse_projection_pushdown --features parquet

Are there any user-facing changes?

No API changes. Queries that repeat an expensive expression over a file scan run faster; their physical plans retain a ProjectionExec above the scan (the expression evaluated once) instead of inlining it into the DataSourceExec projection.

@github-actions github-actions Bot added core Core DataFusion crate sqllogictest SQL Logic Tests (.slt) datasource Changes to the datasource crate labels Jul 10, 2026
@fordN

fordN commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

@xudong963 here's a follow up to #23395 that expands the set of expressions to not be "re-inline'd" to include performance considerations along with the correctness considerations.

In practice, this change has shown to have a significant effect on the performance of queries where outer statement references expensive computation results from inner.

fordN added 2 commits July 10, 2026 12:04
Add a benchmark measuring queries that repeat a scalar function call, which the
logical CSE pass extracts into a single intermediate projection referenced by
column. It exercises the interaction between CSE and projection pushdown on
parquet sources; a follow-up will use it to demonstrate reducing redundant
re-evaluation of the extracted expression.
Extend the projection-pushdown guard so it also declines to merge a projection
into a file scan when the merge would duplicate a non-trivial expression (e.g.
`power(a, 2)`, `a + b`, `c2 >= 2`) that CSE extracted into a shared intermediate
projection. Without this the expression is re-inlined at every reference site
and re-evaluated per row, undoing the deduplication.

"Non-trivial" is decided by expression placement (`KeepInPlace`) — the same
signal `try_collapse_projection_chain` uses — so leaf-pushable expressions
(columns, `get_field`, `input_file_name`) still merge. References are counted
with multiplicity (reused from the volatile guard), so an expression is blocked
only when referenced more than once; volatile (correctness, apache#23220) and
non-trivial (performance) share the same threshold.
@fordN fordN force-pushed the perf/cse-projection-pushdown branch from 80096e0 to 8b7250a Compare July 10, 2026 19:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core Core DataFusion crate datasource Changes to the datasource crate sqllogictest SQL Logic Tests (.slt)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Preserve CSE for expensive expressions when pushing projections into file scans

1 participant