-
Notifications
You must be signed in to change notification settings - Fork 29k
[SPARK-35940][SQL] Refactor EquivalentExpressions to make it more efficient #33142
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This partially solves the perf issue mentioned in https://github.com/apache/spark/pull/32559/files#r633488455
By filtering with height first, we can reduce the data to iterate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I opened #33281 to improve it further.
...atalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/EquivalentExpressions.scala
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure we can trigger this bug with some real queries, but it's an obvious bug to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good catch!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think if we wrongly recurse into the children of CodegenFallback, it only produces unused subexpressions. Some redundant generated codes, i.e..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it better to backport this part into branch-3.1/3.0?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yea will do
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fixes #30245 (comment)
Basically it takes all the conditions as the commonChildrenToRecurse, so that we only get the common expressions that appear in all the conditions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Kimahriman I think this fix works? The only drawback is, if there are common subexpressions among the conditions, they will always be counted as "appear twice" and gets codegened into methods.
I think the perf overhead is really small, and if the first condition is false, we evaluate the next condition which gives perf improvement because of common subexpressions elimination.
For the value branches of CaseWhen, I don't touch them in this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah this definitely fixes a potential bug of creating subexpressions for things that are never evaluated, same with the coalesce update. I think the values are already handled fine, it's just the conditionals that had an issue with short circuiting
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fixed #30245 (comment).
The only drawback is, if there are common subexpressions among the conditions, they will always be counted as "appear twice" and gets codegened into methods.
I just don't get this. You mean for If(a + b > 1, 1, a + b + c > 1, 2, a + b + c > 2, 3), a + b + c will be counted twice and considered as common subexpression?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think he means in CaseWhen(a + b > 1, 1, a + b + c > 1, 2), a + b will be a subexpression even though it might only be executed once.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But CaseWhen(a + b > 1, 1, a + b + c > 1, 2, a + b + c > 0, 3), a + b + c won't even be considered for a subexpression if it's seen elsewhere, which was the bug if CaseWhen supports short circuiting
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, because the first condition of CaseWhen is in both childrenToRecurse and commonChildrenToRecurse
|
Test build #140399 has finished for PR 33142 at commit
|
About this, I think the sorting is not reliable as it is hard to do child-parent sort. I have another proposal to get rid of the sort as I mentioned before. |
|
Can you briefly introduce your idea? Sorting by height is stable and fast now. And I need the height anyway in #33142 (comment) |
Basically, the steps are:
|
|
Test build #140441 has finished for PR 33142 at commit
|
|
Kubernetes integration test unable to build dist. exiting with code: 1 |
I've not looked in the details yet. Is sorting by height guaranteed to sort expressions by child-parent? I said current sorting is not reliable because it might miss some cases probably. It is because two expressions with no child-parent relation has no clear comparison order. So sorting is somehow unreliable for expressions. Does sorting by height solve it? |
| * Instead of appending to a mutable list/buffer of Expressions, just update the "flattened" | ||
| * useCount in this wrapper in-place. | ||
| * | ||
| * This also tracks the "height" of the expression, so that we can return expressions with smaller |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the "height" of the expression -> track the "height" of common subexpressions?
| if (!skip && !addExprToMap(expr, map)) { | ||
| val height = childrenToRecurse(expr).map(addExprTree0(_, map)) | ||
| .reduceOption(_ max _).map(_ + 1).getOrElse(0) | ||
| map(ExpressionEquals(expr)).height = height |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(My comment is the same with the @viirya one) we can always judge if an expr is a parent of another expr or not from this height? It seems this height depends on a map state, so a true height value can change after the assignment? For this purpose, we cannot simply use the height of an expression instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that @viirya will remove the sort, I think this issue doesn't matter now (not worse than before). In addCommonExprs, I only use this height to do filtering, which should be OK.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For this purpose, we cannot simply use the height of an expression instead?
makes sense. A true height (from root to the furthest leaf) is good enough to quickly check child-parent relationship.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it better to backport this part into branch-3.1/3.0?
As long as the sorting algorithm is stable (i.e. retain the original input order), it's stable. It's similar to sort strings by length. |
| assert(a.hashCode != b3.hashCode) | ||
| assert(a.semanticEquals(b3)) | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changes in this file is to adapt the new API in EquivalentExpressions. e.g.
getAllEquivalentExprs() -> getAllExprStates
getEquivalentExprs(oneA).size == 1 -> getExprState(oneA).get.useCount == 1
getEquivalentExprs(oneA).exists(_ eq oneA) -> getExprState(oneA).exists(_.expr eq oneA)
|
Kubernetes integration test starting |
|
Kubernetes integration test status success |
|
Test build #140480 has finished for PR 33142 at commit
|
| exprSet.intersect(otherExprSet) | ||
| map: mutable.HashMap[ExpressionEquals, ExpressionStats]): Unit = { | ||
| assert(exprs.length > 1) | ||
| var localEquivalenceMap = mutable.HashMap.empty[ExpressionEquals, ExpressionStats] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This also fixed that, previously, for Or(Coalesce(expr1, expr2, expr2), Coalesce(expr1, expr2, expr2)), expr2 will be extracted and considered as a common subexpression. Currently, no subexpression will be extracted.
|
@maropu Any more comments? Otherwise I will merge this tomorrow. Thanks. |
|
Thanks! Merging to master/branch-3.2. |
…icient ### What changes were proposed in this pull request? This PR uses 2 ideas to make `EquivalentExpressions` more efficient: 1. do not keep all the equivalent expressions, we only need a count 2. track the "height" of common subexpressions, to quickly do child-parent sort, and filter out non-child expressions in `addCommonExprs` This PR also fixes several small bugs (exposed by the refactoring), please see PR comments. ### Why are the changes needed? code cleanup and small perf improvement ### Does this PR introduce _any_ user-facing change? no ### How was this patch tested? existing tests Closes #33142 from cloud-fan/codegen. Authored-by: Wenchen Fan <[email protected]> Signed-off-by: Liang-Chi Hsieh <[email protected]> (cherry picked from commit e6ce220) Signed-off-by: Liang-Chi Hsieh <[email protected]>
What changes were proposed in this pull request?
This PR uses 2 ideas to make
EquivalentExpressionsmore efficient:addCommonExprsThis PR also fixes several small bugs (exposed by the refactoring), please see PR comments.
Why are the changes needed?
code cleanup and small perf improvement
Does this PR introduce any user-facing change?
no
How was this patch tested?
existing tests