use of org.apache.calcite.plan.RelOptCost in project calcite by apache.
the class LoptOptimizeJoinRule method addFactorToTree.
/**
* Adds a new factor into the current join tree. The factor is either pushed
* down into one of the subtrees of the join recursively, or it is added to
* the top of the current tree, whichever yields a better ordering.
*
* @param multiJoin join factors being optimized
* @param semiJoinOpt optimal semijoins for each factor
* @param joinTree current join tree
* @param factorToAdd new factor to be added
* @param factorsNeeded factors that must precede the factor to be added
* @param filtersToAdd filters remaining to be added; filters added to the
* new join tree are removed from the list
* @param selfJoin true if the join being created is a self-join that's
* removable
*
* @return optimal join tree with the new factor added if it is possible to
* add the factor; otherwise, null is returned
*/
private LoptJoinTree addFactorToTree(RelMetadataQuery mq, RelBuilder relBuilder, LoptMultiJoin multiJoin, LoptSemiJoinOptimizer semiJoinOpt, LoptJoinTree joinTree, int factorToAdd, BitSet factorsNeeded, List<RexNode> filtersToAdd, boolean selfJoin) {
// join that can be removed, then create a replacement join
if (multiJoin.isRemovableOuterJoinFactor(factorToAdd)) {
return createReplacementJoin(relBuilder, multiJoin, semiJoinOpt, joinTree, -1, factorToAdd, ImmutableIntList.of(), null, filtersToAdd);
}
// table is in the current join tree
if (multiJoin.getJoinRemovalFactor(factorToAdd) != null) {
return createReplacementSemiJoin(relBuilder, multiJoin, semiJoinOpt, joinTree, factorToAdd, filtersToAdd);
}
// the single factor
if (joinTree == null) {
return new LoptJoinTree(semiJoinOpt.getChosenSemiJoin(factorToAdd), factorToAdd);
}
// Create a temporary copy of the filter list as we may need the
// original list to pass into addToTop(). However, if no tree was
// created by addToTop() because the factor being added is part of
// a self-join, then pass the original filter list so the added
// filters will still be removed from the list.
final List<RexNode> tmpFilters = new ArrayList<>(filtersToAdd);
LoptJoinTree topTree = addToTop(mq, relBuilder, multiJoin, semiJoinOpt, joinTree, factorToAdd, filtersToAdd, selfJoin);
LoptJoinTree pushDownTree = pushDownFactor(mq, relBuilder, multiJoin, semiJoinOpt, joinTree, factorToAdd, factorsNeeded, (topTree == null) ? filtersToAdd : tmpFilters, selfJoin);
// pick the lower cost option, and replace the join ordering with
// the ordering associated with the best option
LoptJoinTree bestTree;
RelOptCost costPushDown = null;
RelOptCost costTop = null;
if (pushDownTree != null) {
costPushDown = mq.getCumulativeCost(pushDownTree.getJoinTree());
}
if (topTree != null) {
costTop = mq.getCumulativeCost(topTree.getJoinTree());
}
if (pushDownTree == null) {
bestTree = topTree;
} else if (topTree == null) {
bestTree = pushDownTree;
} else {
if (costPushDown.isEqWithEpsilon(costTop)) {
// around the wider rows further up in the tree
if (rowWidthCost(pushDownTree.getJoinTree()) < rowWidthCost(topTree.getJoinTree())) {
bestTree = pushDownTree;
} else {
bestTree = topTree;
}
} else if (costPushDown.isLt(costTop)) {
bestTree = pushDownTree;
} else {
bestTree = topTree;
}
}
return bestTree;
}
use of org.apache.calcite.plan.RelOptCost in project calcite by apache.
the class RelSubset method propagateCostImprovements0.
void propagateCostImprovements0(VolcanoPlanner planner, RelMetadataQuery mq, RelNode rel, Set<RelSubset> activeSet) {
++timestamp;
if (!activeSet.add(this)) {
// This subset is already in the chain being propagated to. This
// means that the graph is cyclic, and therefore the cost of this
// relational expression - not this subset - must be infinite.
LOGGER.trace("cyclic: {}", this);
return;
}
try {
final RelOptCost cost = planner.getCost(rel, mq);
if (cost.isLt(bestCost)) {
LOGGER.trace("Subset cost improved: subset [{}] cost was {} now {}", this, bestCost, cost);
bestCost = cost;
best = rel;
// Lower cost means lower importance. Other nodes will change
// too, but we'll get to them later.
planner.ruleQueue.recompute(this);
for (RelNode parent : getParents()) {
final RelSubset parentSubset = planner.getSubset(parent);
parentSubset.propagateCostImprovements(planner, mq, parent, activeSet);
}
planner.checkForSatisfiedConverters(set, rel);
}
} finally {
activeSet.remove(this);
}
}
use of org.apache.calcite.plan.RelOptCost in project calcite by apache.
the class VolcanoPlanner method findBestExp.
/**
* Finds the most efficient expression to implement the query given via
* {@link org.apache.calcite.plan.RelOptPlanner#setRoot(org.apache.calcite.rel.RelNode)}.
*
* <p>The algorithm executes repeatedly in a series of phases. In each phase
* the exact rules that may be fired varies. The mapping of phases to rule
* sets is maintained in the {@link #ruleQueue}.
*
* <p>In each phase, the planner sets the initial importance of the existing
* RelSubSets ({@link #setInitialImportance()}). The planner then iterates
* over the rule matches presented by the rule queue until:
*
* <ol>
* <li>The rule queue becomes empty.</li>
* <li>For ambitious planners: No improvements to the plan have been made
* recently (specifically within a number of iterations that is 10% of the
* number of iterations necessary to first reach an implementable plan or 25
* iterations whichever is larger).</li>
* <li>For non-ambitious planners: When an implementable plan is found.</li>
* </ol>
*
* <p>Furthermore, after every 10 iterations without an implementable plan,
* RelSubSets that contain only logical RelNodes are given an importance
* boost via {@link #injectImportanceBoost()}. Once an implementable plan is
* found, the artificially raised importance values are cleared (see
* {@link #clearImportanceBoost()}).
*
* @return the most efficient RelNode tree found for implementing the given
* query
*/
public RelNode findBestExp() {
ensureRootConverters();
registerMaterializations();
int cumulativeTicks = 0;
for (VolcanoPlannerPhase phase : VolcanoPlannerPhase.values()) {
setInitialImportance();
RelOptCost targetCost = costFactory.makeHugeCost();
int tick = 0;
int firstFiniteTick = -1;
int splitCount = 0;
int giveUpTick = Integer.MAX_VALUE;
while (true) {
++tick;
++cumulativeTicks;
if (root.bestCost.isLe(targetCost)) {
if (firstFiniteTick < 0) {
firstFiniteTick = cumulativeTicks;
clearImportanceBoost();
}
if (ambitious) {
// Choose a slightly more ambitious target cost, and
// try again. If it took us 1000 iterations to find our
// first finite plan, give ourselves another 100
// iterations to reduce the cost by 10%.
targetCost = root.bestCost.multiplyBy(0.9);
++splitCount;
if (impatient) {
if (firstFiniteTick < 10) {
// It's possible pre-processing can create
// an implementable plan -- give us some time
// to actually optimize it.
giveUpTick = cumulativeTicks + 25;
} else {
giveUpTick = cumulativeTicks + Math.max(firstFiniteTick / 10, 25);
}
}
} else {
break;
}
} else if (cumulativeTicks > giveUpTick) {
// We haven't made progress recently. Take the current best.
break;
} else if (root.bestCost.isInfinite() && ((tick % 10) == 0)) {
injectImportanceBoost();
}
LOGGER.debug("PLANNER = {}; TICK = {}/{}; PHASE = {}; COST = {}", this, cumulativeTicks, tick, phase.toString(), root.bestCost);
VolcanoRuleMatch match = ruleQueue.popMatch(phase);
if (match == null) {
break;
}
assert match.getRule().matches(match);
match.onMatch();
// The root may have been merged with another
// subset. Find the new root subset.
root = canonize(root);
}
ruleQueue.phaseCompleted(phase);
}
if (LOGGER.isTraceEnabled()) {
StringWriter sw = new StringWriter();
final PrintWriter pw = new PrintWriter(sw);
dump(pw);
pw.flush();
LOGGER.trace(sw.toString());
}
RelNode cheapest = root.buildCheapestPlan(this);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Cheapest plan:\n{}", RelOptUtil.toString(cheapest, SqlExplainLevel.ALL_ATTRIBUTES));
LOGGER.debug("Provenance:\n{}", provenance(cheapest));
}
return cheapest;
}
use of org.apache.calcite.plan.RelOptCost in project calcite by apache.
the class HepPlanner method applyTransformationResults.
private HepRelVertex applyTransformationResults(HepRelVertex vertex, HepRuleCall call, RelTrait parentTrait) {
assert !call.getResults().isEmpty();
RelNode bestRel = null;
if (call.getResults().size() == 1) {
// No costing required; skip it to minimize the chance of hitting
// rels without cost information.
bestRel = call.getResults().get(0);
} else {
RelOptCost bestCost = null;
final RelMetadataQuery mq = call.getMetadataQuery();
for (RelNode rel : call.getResults()) {
RelOptCost thisCost = getCost(rel, mq);
if (LOGGER.isTraceEnabled()) {
// Keep in the isTraceEnabled for the getRowCount method call
LOGGER.trace("considering {} with cumulative cost={} and rowcount={}", rel, thisCost, mq.getRowCount(rel));
}
if ((bestRel == null) || thisCost.isLt(bestCost)) {
bestRel = rel;
bestCost = thisCost;
}
}
}
++nTransformations;
notifyTransformation(call, bestRel, true);
// Before we add the result, make a copy of the list of vertex's
// parents. We'll need this later during contraction so that
// we only update the existing parents, not the new parents
// (otherwise loops can result). Also take care of filtering
// out parents by traits in case we're dealing with a converter rule.
final List<HepRelVertex> allParents = Graphs.predecessorListOf(graph, vertex);
final List<HepRelVertex> parents = new ArrayList<>();
for (HepRelVertex parent : allParents) {
if (parentTrait != null) {
RelNode parentRel = parent.getCurrentRel();
if (parentRel instanceof Converter) {
// the multi-parent DAG case.
continue;
}
if (!parentRel.getTraitSet().contains(parentTrait)) {
// This parent does not want the converted result.
continue;
}
}
parents.add(parent);
}
HepRelVertex newVertex = addRelToGraph(bestRel);
// There's a chance that newVertex is the same as one
// of the parents due to common subexpression recognition
// (e.g. the LogicalProject added by JoinCommuteRule). In that
// case, treat the transformation as a nop to avoid
// creating a loop.
int iParentMatch = parents.indexOf(newVertex);
if (iParentMatch != -1) {
newVertex = parents.get(iParentMatch);
} else {
contractVertices(newVertex, vertex, parents);
}
if (getListener() != null) {
// Assume listener doesn't want to see garbage.
collectGarbage();
}
notifyTransformation(call, bestRel, false);
dumpGraph();
return newVertex;
}
Aggregations