Skip to content

Commit f181e07

Browse files
zhuwenzhuangmihaibudiu
authored andcommitted
[CALCITE-7416] Add firedRulesCache for HepPlanner
1 parent e810d8b commit f181e07

2 files changed

Lines changed: 88 additions & 5 deletions

File tree

core/src/main/java/org/apache/calcite/plan/hep/HepPlanner.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@
5151
import org.apache.calcite.util.graph.Graphs;
5252
import org.apache.calcite.util.graph.TopologicalOrderIterator;
5353

54+
import com.google.common.collect.HashMultimap;
5455
import com.google.common.collect.ImmutableList;
56+
import com.google.common.collect.Multimap;
5557

5658
import org.checkerframework.checker.nullness.qual.Nullable;
5759

@@ -66,6 +68,7 @@
6668
import java.util.Map;
6769
import java.util.Queue;
6870
import java.util.Set;
71+
import java.util.stream.Collectors;
6972

7073
import static com.google.common.base.Preconditions.checkArgument;
7174

@@ -114,6 +117,30 @@ public class HepPlanner extends AbstractRelOptPlanner {
114117
private final List<RelOptMaterialization> materializations =
115118
new ArrayList<>();
116119

120+
/**
121+
* Cache of rules that have already been fired for a specific operand match,
122+
* to avoid firing the same rule repeatedly.
123+
*
124+
* <p>Key: the list of matched {@link RelNode} IDs (operand match).
125+
*
126+
* <p>Value: the set of {@link RelOptRule}s already fired for that exact ID list.
127+
*/
128+
private final Multimap<List<Integer>, RelOptRule> firedRulesCache = HashMultimap.create();
129+
130+
/**
131+
* Reverse index for {@link #firedRulesCache}, used for cleanup/GC:
132+
* maps a single {@link RelNode} ID to all match-key ID lists that include it,
133+
* so related cache entries can be removed efficiently when a node is discarded.
134+
*
135+
* <p>Key: {@link RelNode} ID.
136+
*
137+
* <p>Value: match-key ID lists in {@link #firedRulesCache} that contain the key ID.
138+
*/
139+
private final Multimap<Integer, List<Integer>> firedRulesCacheIndex = HashMultimap.create();
140+
141+
142+
private boolean enableFiredRulesCache = false;
143+
117144
//~ Constructors -----------------------------------------------------------
118145

119146
/**
@@ -173,6 +200,8 @@ public HepPlanner(
173200
removeRule(rule);
174201
}
175202
this.materializations.clear();
203+
this.firedRulesCache.clear();
204+
this.firedRulesCacheIndex.clear();
176205
}
177206

178207
@Override public RelNode changeTraits(RelNode rel, RelTraitSet toTraits) {
@@ -195,6 +224,17 @@ public HepPlanner(
195224
return buildFinalPlan(requireNonNull(root, "'root' must not be null"));
196225
}
197226

227+
/**
228+
* Enables or disables the fire-rule cache.
229+
*
230+
* <p> If enabled, a rule will not fire twice on the same {@code RelNode::getId()}.
231+
*
232+
* @param enable true to enable; false is default value.
233+
*/
234+
public void setEnableFiredRulesCache(boolean enable) {
235+
enableFiredRulesCache = enable;
236+
}
237+
198238
/** Top-level entry point for a program. Initializes state and then invokes
199239
* the program. */
200240
private void executeProgram(HepProgram program) {
@@ -519,13 +559,28 @@ private Iterator<HepRelVertex> getGraphIterator(
519559
nodeChildren,
520560
parents);
521561

562+
List<Integer> relIds = null;
563+
if (enableFiredRulesCache) {
564+
relIds = call.getRelList().stream().map(RelNode::getId).collect(Collectors.toList());
565+
if (firedRulesCache.get(relIds).contains(rule)) {
566+
return null;
567+
}
568+
}
569+
522570
// Allow the rule to apply its own side-conditions.
523571
if (!rule.matches(call)) {
524572
return null;
525573
}
526574

527575
fireRule(call);
528576

577+
if (relIds != null) {
578+
firedRulesCache.put(relIds, rule);
579+
for (Integer relId : relIds) {
580+
firedRulesCacheIndex.put(relId, relIds);
581+
}
582+
}
583+
529584
if (!call.getResults().isEmpty()) {
530585
return applyTransformationResults(
531586
vertex,
@@ -982,6 +1037,15 @@ private void collectGarbage() {
9821037

9831038
// Clean up metadata cache too.
9841039
sweepSet.forEach(this::clearCache);
1040+
1041+
if (enableFiredRulesCache) {
1042+
sweepSet.forEach(rel -> {
1043+
for (List<Integer> relIds : firedRulesCacheIndex.get(rel.getCurrentRel().getId())) {
1044+
firedRulesCache.removeAll(relIds);
1045+
}
1046+
firedRulesCacheIndex.removeAll(rel.getCurrentRel().getId());
1047+
});
1048+
}
9851049
}
9861050

9871051
private void assertNoCycles() {

core/src/test/java/org/apache/calcite/test/HepPlannerTest.java

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -366,11 +366,29 @@ private void assertIncludesExactlyOnce(String message, String digest,
366366
}
367367

368368
@Test void testRuleApplyCount() {
369-
final long applyTimes1 = checkRuleApplyCount(HepMatchOrder.ARBITRARY);
370-
assertThat(applyTimes1, is(316L));
369+
long applyTimes = checkRuleApplyCount(HepMatchOrder.ARBITRARY, false);
370+
assertThat(applyTimes, is(316L));
371371

372-
final long applyTimes2 = checkRuleApplyCount(HepMatchOrder.DEPTH_FIRST);
373-
assertThat(applyTimes2, is(87L));
372+
applyTimes = checkRuleApplyCount(HepMatchOrder.DEPTH_FIRST, false);
373+
assertThat(applyTimes, is(87L));
374+
375+
applyTimes = checkRuleApplyCount(HepMatchOrder.TOP_DOWN, false);
376+
assertThat(applyTimes, is(295L));
377+
378+
applyTimes = checkRuleApplyCount(HepMatchOrder.BOTTOM_UP, false);
379+
assertThat(applyTimes, is(296L));
380+
381+
applyTimes = checkRuleApplyCount(HepMatchOrder.ARBITRARY, true);
382+
assertThat(applyTimes, is(65L));
383+
384+
applyTimes = checkRuleApplyCount(HepMatchOrder.DEPTH_FIRST, true);
385+
assertThat(applyTimes, is(65L));
386+
387+
applyTimes = checkRuleApplyCount(HepMatchOrder.TOP_DOWN, true);
388+
assertThat(applyTimes, is(65L));
389+
390+
applyTimes = checkRuleApplyCount(HepMatchOrder.BOTTOM_UP, true);
391+
assertThat(applyTimes, is(65L));
374392
}
375393

376394
@Test void testMaterialization() {
@@ -387,7 +405,7 @@ private void assertIncludesExactlyOnce(String message, String digest,
387405
assertThat(planner.getMaterializations(), empty());
388406
}
389407

390-
private long checkRuleApplyCount(HepMatchOrder matchOrder) {
408+
private long checkRuleApplyCount(HepMatchOrder matchOrder, boolean enableFiredRulesCache) {
391409
final HepProgramBuilder programBuilder = HepProgram.builder();
392410
programBuilder.addMatchOrder(matchOrder);
393411
programBuilder.addRuleInstance(CoreRules.FILTER_REDUCE_EXPRESSIONS);
@@ -397,6 +415,7 @@ private long checkRuleApplyCount(HepMatchOrder matchOrder) {
397415
HepPlanner planner = new HepPlanner(programBuilder.build());
398416
planner.addListener(listener);
399417
planner.setRoot(sql(COMPLEX_UNION_TREE).toRel());
418+
planner.setEnableFiredRulesCache(enableFiredRulesCache);
400419
planner.findBestExp();
401420
return listener.getApplyTimes();
402421
}

0 commit comments

Comments
 (0)