Skip to content
This repository was archived by the owner on Jan 7, 2025. It is now read-only.

Commit bdf638a

Browse files
authored
fix(core): improve dead-loop avoidance in OptimizeInputs (v2) (#206)
Currently, OptimizeInputsTask avoids a dead-loop with a sequential traversal of a potentially long list of tasks, with a dynamic cast for each of them (see #29). This alternative uses a hashset of previously visited expressions for the same purpose, similarly to explored_group. This is the second attempt, after #196.
1 parent 4096073 commit bdf638a

File tree

7 files changed

+22
-29
lines changed

7 files changed

+22
-29
lines changed

optd-core/src/cascades/optimizer.rs

+16
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ pub struct CascadesOptimizer<T: RelNodeTyp> {
4343
memo: Memo<T>,
4444
pub(super) tasks: VecDeque<Box<dyn Task<T>>>,
4545
explored_group: HashSet<GroupId>,
46+
explored_expr: HashSet<ExprId>,
4647
fired_rules: HashMap<ExprId, HashSet<RuleId>>,
4748
rules: Arc<[Arc<RuleWrapper<T, Self>>]>,
4849
disabled_rules: HashSet<usize>,
@@ -105,6 +106,7 @@ impl<T: RelNodeTyp> CascadesOptimizer<T> {
105106
memo,
106107
tasks,
107108
explored_group: HashSet::new(),
109+
explored_expr: HashSet::new(),
108110
fired_rules: HashMap::new(),
109111
rules: rules.into(),
110112
cost: cost.into(),
@@ -202,11 +204,13 @@ impl<T: RelNodeTyp> CascadesOptimizer<T> {
202204
self.memo = Memo::new(self.property_builders.clone());
203205
self.fired_rules.clear();
204206
self.explored_group.clear();
207+
self.explored_expr.clear();
205208
}
206209

207210
/// Clear the winner so that the optimizer can continue to explore the group.
208211
pub fn step_clear_winner(&mut self) {
209212
self.memo.clear_winner();
213+
self.explored_expr.clear();
210214
}
211215

212216
/// Optimize a `RelNode`.
@@ -347,6 +351,18 @@ impl<T: RelNodeTyp> CascadesOptimizer<T> {
347351
self.explored_group.insert(group_id);
348352
}
349353

354+
pub(super) fn is_expr_explored(&self, expr_id: ExprId) -> bool {
355+
self.explored_expr.contains(&expr_id)
356+
}
357+
358+
pub(super) fn mark_expr_explored(&mut self, expr_id: ExprId) {
359+
self.explored_expr.insert(expr_id);
360+
}
361+
362+
pub(super) fn unmark_expr_explored(&mut self, expr_id: ExprId) {
363+
self.explored_expr.remove(&expr_id);
364+
}
365+
350366
pub(super) fn is_rule_fired(&self, group_expr_id: ExprId, rule_id: RuleId) -> bool {
351367
self.fired_rules
352368
.get(&group_expr_id)

optd-core/src/cascades/tasks.rs

-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,5 @@ pub use optimize_inputs::OptimizeInputsTask;
1818

1919
pub trait Task<T: RelNodeTyp>: 'static + Send + Sync {
2020
fn execute(&self, optimizer: &mut CascadesOptimizer<T>) -> Result<Vec<Box<dyn Task<T>>>>;
21-
fn as_any(&self) -> &dyn std::any::Any;
2221
fn describe(&self) -> String;
2322
}

optd-core/src/cascades/tasks/apply_rule.rs

+1-4
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,6 @@ fn match_and_pick<T: RelNodeTyp>(
172172
}
173173

174174
impl<T: RelNodeTyp> Task<T> for ApplyRuleTask {
175-
fn as_any(&self) -> &dyn std::any::Any {
176-
self
177-
}
178-
179175
fn execute(&self, optimizer: &mut CascadesOptimizer<T>) -> Result<Vec<Box<dyn Task<T>>>> {
180176
if optimizer.is_rule_fired(self.expr_id, self.rule_id) {
181177
return Ok(vec![]);
@@ -215,6 +211,7 @@ impl<T: RelNodeTyp> Task<T> for ApplyRuleTask {
215211
.push(Box::new(OptimizeInputsTask::new(expr_id, true))
216212
as Box<dyn Task<T>>);
217213
}
214+
optimizer.unmark_expr_explored(expr_id);
218215
trace!(event = "apply_rule", expr_id = %self.expr_id, rule_id = %self.rule_id, new_expr_id = %expr_id);
219216
} else {
220217
trace!(event = "apply_rule", expr_id = %self.expr_id, rule_id = %self.rule_id, "triggered group merge");

optd-core/src/cascades/tasks/explore_group.rs

-4
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,6 @@ impl ExploreGroupTask {
2222
}
2323

2424
impl<T: RelNodeTyp> Task<T> for ExploreGroupTask {
25-
fn as_any(&self) -> &dyn std::any::Any {
26-
self
27-
}
28-
2925
fn execute(&self, optimizer: &mut CascadesOptimizer<T>) -> Result<Vec<Box<dyn Task<T>>>> {
3026
trace!(event = "task_begin", task = "explore_group", group_id = %self.group_id);
3127
let mut tasks = vec![];

optd-core/src/cascades/tasks/optimize_expression.rs

-4
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,6 @@ fn top_matches<T: RelNodeTyp>(
3636
}
3737

3838
impl<T: RelNodeTyp> Task<T> for OptimizeExpressionTask {
39-
fn as_any(&self) -> &dyn std::any::Any {
40-
self
41-
}
42-
4339
fn execute(&self, optimizer: &mut CascadesOptimizer<T>) -> Result<Vec<Box<dyn Task<T>>>> {
4440
let expr = optimizer.get_expr_memoed(self.expr_id);
4541
trace!(event = "task_begin", task = "optimize_expr", expr_id = %self.expr_id, expr = %expr);

optd-core/src/cascades/tasks/optimize_group.rs

-4
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,6 @@ impl OptimizeGroupTask {
2323
}
2424

2525
impl<T: RelNodeTyp> Task<T> for OptimizeGroupTask {
26-
fn as_any(&self) -> &dyn std::any::Any {
27-
self
28-
}
29-
3026
fn execute(&self, optimizer: &mut CascadesOptimizer<T>) -> Result<Vec<Box<dyn Task<T>>>> {
3127
trace!(event = "task_begin", task = "optimize_group", group_id = %self.group_id);
3228
let group_info = optimizer.get_group_info(self.group_id);

optd-core/src/cascades/tasks/optimize_inputs.rs

+5-12
Original file line numberDiff line numberDiff line change
@@ -117,22 +117,15 @@ impl OptimizeInputsTask {
117117
}
118118

119119
impl<T: RelNodeTyp> Task<T> for OptimizeInputsTask {
120-
fn as_any(&self) -> &dyn std::any::Any {
121-
self
122-
}
123-
124120
fn execute(&self, optimizer: &mut CascadesOptimizer<T>) -> Result<Vec<Box<dyn Task<T>>>> {
125-
if optimizer.tasks.iter().any(|t| {
126-
if let Some(task) = t.as_any().downcast_ref::<Self>() {
121+
if self.continue_from.is_none() {
122+
if optimizer.is_expr_explored(self.expr_id) {
127123
// skip optimize_inputs to avoid dead-loop: consider join commute being fired twice that produces
128124
// two projections, therefore having groups like projection1 -> projection2 -> join = projection1.
129-
task.expr_id == self.expr_id
130-
} else {
131-
false
125+
trace!(event = "task_skip", task = "optimize_inputs", expr_id = %self.expr_id);
126+
return Ok(vec![]);
132127
}
133-
}) {
134-
trace!(event = "task_skip", task = "optimize_inputs", expr_id = %self.expr_id);
135-
return Ok(vec![]);
128+
optimizer.mark_expr_explored(self.expr_id);
136129
}
137130
trace!(event = "task_begin", task = "optimize_inputs", expr_id = %self.expr_id, continue_from = ?self.continue_from);
138131
let expr = optimizer.get_expr_memoed(self.expr_id);

0 commit comments

Comments
 (0)