dongjoon-hyun commented on code in PR #59143:
URL: https://github.com/apache/spark/pull/59143#discussion_r4328857778


##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/predicates.scala:
##########
@@ -185,6 +185,16 @@ trait PredicateHelper extends AliasHelper with Logging {
     currentResult.head
   }
 
+  /**
+   * Option-returning form of [[buildBalancedPredicate]]: `None` for an empty 
input, otherwise the
+   * combined predicate.
+   */
+  protected def buildBalancedPredicateOption(
+      expressions: Seq[Expression],
+      op: (Expression, Expression) => Expression): Option[Expression] = {
+    if (expressions.isEmpty) None else 
Some(buildBalancedPredicate(expressions, op))

Review Comment:
   **The first pass of `buildBalancedPredicate` is quadratic on a `List`, and 
this wrapper passes its input through unchanged.** HyukjinKwon asked about this 
in review 5405036205, and the helper is unchanged at 774871c. At L171-183, 
`currentResult` starts as the caller's `Seq`, and the first pass calls 
`currentResult.size` in both loop conditions, in the array length and in `i + 1 
== currentResult.size`, plus `currentResult(i)` and `currentResult(i + 1)`. On 
a `List`, each of these walks the list, so the first pass costs about 1.5 * n^2 
node steps, where the replaced `reduceLeft` was O(n).
   
   The `ExpressionSet` callers pass `List`s too. `ExpressionSet` does not 
override `toSeq`, so it is `immutable.Seq.from(this)`, which builds a `List`. 
That covers `newFilters.toSeq` and `newPredicates.toSeq` in 
`InferFiltersFromConstraints` (Optimizer.scala:2255 and :2306), the 
`CombineFilters` difference (Optimizer.scala:2407) and `joinConds.toSeq` 
(CostBasedJoinReorder.scala:308).
   
   The cost is also paid repeatedly. The `Join` arm of 
`PushPredicateThroughJoin` rebuilds every join condition on every fixed-point 
iteration, and `ExtractEquiJoinKeys.unapply` (patterns.scala:223-224) builds 
both `otherPredicates` and `predicatesOfJoinKeys` on every match, although none 
of its 16 match sites in `sql/` reads the latter. Where master re-split a 
left-deep tree, its recursive split was already quadratic, so those paths only 
move the cost. But a `FullOuter` join condition from SQL, which the parser 
builds balanced and `PushPredicateThroughJoin` never rebuilds, now costs O(n^2) 
instead of O(n log n) on each `ExtractEquiJoinKeys` match. I traced this by 
reading the code and did not measure it.
   
   Could you change L171 to `var currentResult: Seq[Expression] = 
expressions.toIndexedSeq`? It is a no-op for an `ArraySeq` or a `Vector`, keeps 
the same pairing and tree shape, and makes the helper O(n) for every caller.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to