Optimize JoinCondition matching (#9200)

* Optimize JoinCondition matching

The LookupJoinMatcher needs to check if a condition is always true or false
multiple times. This can be pre-computed to speed up the match checking

This change reduces the time it takes to perform a for joining on a long key
from ~ 36 ms/op to 23 ms/ op

* Rename variables

* fix typo
This commit is contained in:
Suneet Saldanha 2020-01-21 09:11:50 -08:00 committed by Fangjin Yang
parent f511af1306
commit a2939bbd1a
2 changed files with 21 additions and 9 deletions

View File

@ -28,6 +28,7 @@ import org.apache.druid.math.expr.Parser;
import org.apache.druid.query.expression.ExprUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@ -50,6 +51,9 @@ public class JoinConditionAnalysis
private final String originalExpression;
private final List<Equality> equiConditions;
private final List<Expr> nonEquiConditions;
private final boolean isAlwaysFalse;
private final boolean isAlwaysTrue;
private final boolean canHashJoin;
private JoinConditionAnalysis(
final String originalExpression,
@ -58,8 +62,15 @@ public class JoinConditionAnalysis
)
{
this.originalExpression = Preconditions.checkNotNull(originalExpression, "originalExpression");
this.equiConditions = equiConditions;
this.nonEquiConditions = nonEquiConditions;
this.equiConditions = Collections.unmodifiableList(equiConditions);
this.nonEquiConditions = Collections.unmodifiableList(nonEquiConditions);
// if any nonEquiCondition is an expression and it evaluates to false
isAlwaysFalse = nonEquiConditions.stream()
.anyMatch(expr -> expr.isLiteral() && !expr.eval(ExprUtils.nilBindings()).asBoolean());
// if there are no equiConditions and all nonEquiConditions are literals and the evaluate to true
isAlwaysTrue = equiConditions.isEmpty() && nonEquiConditions.stream()
.allMatch(expr -> expr.isLiteral() && expr.eval(ExprUtils.nilBindings()).asBoolean());
canHashJoin = nonEquiConditions.stream().allMatch(Expr::isLiteral);
}
/**
@ -141,8 +152,7 @@ public class JoinConditionAnalysis
*/
public boolean isAlwaysFalse()
{
return nonEquiConditions.stream()
.anyMatch(expr -> expr.isLiteral() && !expr.eval(ExprUtils.nilBindings()).asBoolean());
return isAlwaysFalse;
}
/**
@ -150,9 +160,7 @@ public class JoinConditionAnalysis
*/
public boolean isAlwaysTrue()
{
return equiConditions.isEmpty() &&
nonEquiConditions.stream()
.allMatch(expr -> expr.isLiteral() && expr.eval(ExprUtils.nilBindings()).asBoolean());
return isAlwaysTrue;
}
/**
@ -160,7 +168,7 @@ public class JoinConditionAnalysis
*/
public boolean canHashJoin()
{
return nonEquiConditions.stream().allMatch(Expr::isLiteral);
return canHashJoin;
}
@Override

View File

@ -267,7 +267,11 @@ public class JoinConditionAnalysisTest
{
EqualsVerifier.forClass(JoinConditionAnalysis.class)
.usingGetClass()
.withIgnoredFields("equiConditions", "nonEquiConditions")
.withIgnoredFields(
// These fields are tightly coupled with originalExpression
"equiConditions", "nonEquiConditions",
// These fields are calculated from nonEquiConditions
"isAlwaysTrue", "isAlwaysFalse", "canHashJoin")
.verify();
}