[SPARK-59614][SQL] Compile the AS-OF join as-of/residual conditions to a Predicate - #58886
Open
david-mollitor-db wants to merge 1 commit into
Open
david-mollitor-db wants to merge 1 commit into
david-mollitor-db wants to merge 1 commit into
Conversation
…o a Predicate ### What changes were proposed in this pull request? `SortMergeAsOfJoinScanner` (`sql/core/src/main/scala/org/apache/spark/sql/execution/joins/SortMergeAsOfJoinExec.scala`) evaluated its as-of and residual conditions as bound `Expression`s, interpreted per right-buffer row (`boundAsOfCond.eval(joinedRow)` and `boundResidualCond.forall(...)`). This compiles them to `BasePredicate` via `Predicate.create(...)` and evaluates them through the primitive-returning `eval(InternalRow): Boolean`: - `boundAsOfCond` becomes a `BasePredicate`; `boundResidualCond` becomes `Option[BasePredicate]`. - Each predicate is initialized per partition (`initialize(partitionIndex)`, index from `TaskContext.getPartitionId()`), matching the `CartesianProductExec` idiom. - The call sites in `findBestBackwardForward`/`findBestForwardNearest` use `if (boundAsOfCond.eval(joinedRow))` plus a small `residualHolds` helper that avoids the `Option.forall` closure and its boxed result. - The `orderExpression` (a distance value, not a boolean) intentionally stays a bound `Expression`. ### Why are the changes needed? `SortMergeAsOfJoinScanner` has no whole-stage codegen, so its inner scan is always interpreted. Interpreted `Expression.eval` walks the expression tree and `BoundReference.eval` boxes every operand it reads into non-cached `java.lang.Double`/`java.lang.Long` objects. JFR profiling of `AsOfJoinBenchmark` (sort-merge cases isolated) showed this dominated the scan: ~24% of CPU (`boxToInteger` 17.6% + `Double.valueOf` 4.1% + `Long.valueOf` 2.0%) and ~33% of allocation (`Double` 20% + `Long` 13%). Compiling the conditions removes both the tree-walk and the boxing; the profile's boxing/interpreted-eval frames are replaced by a single generated `SpecificPredicate.eval`, and a no-equi-key `AsOfJoinBenchmark` run improved ~14% (929ms -> 800ms best time). ### Does this PR introduce _any_ user-facing change? No. `Predicate.eval` returns `false` for a null/false result, exactly matching the previous `x != null && x.asInstanceOf[Boolean]` guard, and the binding schema is unchanged. ### How was this patch tested? Existing tests pass: `SortMergeAsOfJoinSuite`, `DataFrameAsOfJoinSuite`, `AsOfJoinSQLSuite`, and `AsOfJoinSortMergeSQLSuite` (89 tests). Before/after JFR on `AsOfJoinBenchmark` confirms the boxing and interpreted-eval frames are eliminated. ### Was this patch authored or co-authored using generative AI tooling? Generated-by: Isaac Co-authored-by: Isaac <no-reply@databricks.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changes were proposed in this pull request?
SortMergeAsOfJoinScanner(
sql/core/src/main/scala/org/apache/spark/sql/execution/joins/SortMergeAsOfJoinExec.scala)evaluated its as-of and residual conditions as bound
Expressions, interpreted per right-bufferrow (
boundAsOfCond.eval(joinedRow)andboundResidualCond.forall(...)). This compiles them toBasePredicateviaPredicate.create(...)and evaluates them through the primitive-returningeval(InternalRow): Boolean:boundAsOfCondbecomes aBasePredicate;boundResidualCondbecomesOption[BasePredicate].initialize(partitionIndex), index fromTaskContext.getPartitionId()), matching theCartesianProductExecidiom.findBestBackwardForward/findBestForwardNearestuseif (boundAsOfCond.eval(joinedRow))plus a smallresidualHoldshelper that avoids theOption.forallclosure and its boxed result.orderExpression(a distance value, not a boolean) intentionally stays a boundExpression.Why are the changes needed?
SortMergeAsOfJoinScannerhas no whole-stage codegen, so its inner scan is always interpreted.Interpreted
Expression.evalwalks the expression tree andBoundReference.evalboxes everyoperand it reads into non-cached
java.lang.Double/java.lang.Longobjects. JFR profiling ofAsOfJoinBenchmark(sort-merge cases isolated) showed this dominated the scan: ~24% of CPU(
boxToInteger17.6% +Double.valueOf4.1% +Long.valueOf2.0%) and ~33% of allocation(
Double20% +Long13%). Compiling the conditions removes both the tree-walk and the boxing;the profile's boxing/interpreted-eval frames are replaced by a single generated
SpecificPredicate.eval, and a no-equi-keyAsOfJoinBenchmarkrun improved ~14% (929ms -> 800msbest time).
Does this PR introduce any user-facing change?
No.
Predicate.evalreturnsfalsefor a null/false result, exactly matching the previousx != null && x.asInstanceOf[Boolean]guard, and the binding schema is unchanged.How was this patch tested?
Existing tests pass:
SortMergeAsOfJoinSuite,DataFrameAsOfJoinSuite,AsOfJoinSQLSuite, andAsOfJoinSortMergeSQLSuite(89 tests). Before/after JFR onAsOfJoinBenchmarkconfirms theboxing and interpreted-eval frames are eliminated.
Was this patch authored or co-authored using generative AI tooling?
Generated-by: Isaac
This pull request and its description were written by Isaac.