Skip to content

Commit 6c41c35

Browse files
committed
Optimize expression plans top down
Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
1 parent 79e129d commit 6c41c35

5 files changed

Lines changed: 171 additions & 27 deletions

File tree

vortex-layout/src/plan/plans/chunked.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ impl PlanParentReduceRule<ChunkedPlan> for ExpressionChunkedRule {
116116
let dtype = expression.dtype().clone();
117117
let chunks = child
118118
.chunks
119-
.try_map(|_, chunk| ExpressionPlan::new(expression.clone(), chunk).optimize())?;
119+
.try_map(|_, chunk| Ok(ExpressionPlan::new_ref(expression.clone(), chunk)))?;
120120
Ok(Some(Arc::new(child.with_chunks(dtype, chunks))))
121121
}
122122
}

vortex-layout/src/plan/plans/dict.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,7 @@ impl PlanParentReduceRule<DictPlan> for ExpressionDictRule {
133133
return Ok(None);
134134
}
135135

136-
let values =
137-
ExpressionPlan::new(expression.clone(), Arc::clone(&child.values)).optimize()?;
136+
let values = ExpressionPlan::new_ref(expression.clone(), Arc::clone(&child.values));
138137
Ok(Some(Arc::new(
139138
child.with_children(Arc::clone(&child.codes), values),
140139
)))

vortex-layout/src/plan/plans/expression.rs

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// SPDX-License-Identifier: Apache-2.0
22
// SPDX-FileCopyrightText: Copyright the Vortex contributors
33

4+
use std::any::TypeId;
45
use std::borrow::Cow;
56
use std::sync::Arc;
67

@@ -40,29 +41,65 @@ impl ExpressionPlan {
4041
pub fn child_plan(&self) -> &PlanRef {
4142
&self.child
4243
}
43-
}
4444

45-
impl Plan for ExpressionPlan {
46-
fn name(&self) -> &'static str {
47-
"ExpressionPlan"
45+
pub(crate) fn new_ref(expression: BoundExpression, child: PlanRef) -> PlanRef {
46+
Arc::new(Self::new(expression, child))
4847
}
4948

50-
fn optimize(&self) -> VortexResult<PlanRef> {
51-
let child = self.child.optimize()?;
49+
fn optimize_top_down(&self, blocked_child_type: Option<TypeId>) -> VortexResult<PlanRef> {
5250
if self.expression.is_root() {
53-
return Ok(child);
51+
return self.child.optimize();
5452
}
53+
if let Some(inner) = self.child.downcast_ref::<Self>() {
54+
let expression = replace_root(self.expression.clone(), inner.expression.clone())?;
55+
return Self::new(expression, Arc::clone(&inner.child)).optimize_top_down(None);
56+
}
57+
58+
let child_type = self.child.as_ref().type_id();
59+
let parent = Self::new_ref(self.expression.clone(), Arc::clone(&self.child));
60+
if blocked_child_type != Some(child_type)
61+
&& let Some(rewritten) = reduce_parent(&parent, 0)?
62+
{
63+
return Self::optimize_rewrite(rewritten, child_type);
64+
}
65+
66+
let child = self.child.optimize()?;
5567
if let Some(inner) = child.downcast_ref::<Self>() {
5668
let expression = replace_root(self.expression.clone(), inner.expression.clone())?;
57-
return Self::new(expression, Arc::clone(&inner.child)).optimize();
69+
return Self::new(expression, Arc::clone(&inner.child)).optimize_top_down(None);
5870
}
59-
let parent: PlanRef = Arc::new(Self::new(self.expression.clone(), child));
60-
if let Some(rewritten) = reduce_parent(&parent, 0)? {
61-
return Ok(rewritten);
71+
72+
let child_type = child.as_ref().type_id();
73+
let parent = Self::new_ref(self.expression.clone(), child);
74+
if blocked_child_type != Some(child_type)
75+
&& let Some(rewritten) = reduce_parent(&parent, 0)?
76+
{
77+
return Self::optimize_rewrite(rewritten, child_type);
6278
}
6379
Ok(parent)
6480
}
6581

82+
fn optimize_rewrite(rewritten: PlanRef, previous_child_type: TypeId) -> VortexResult<PlanRef> {
83+
let Some(expression) = rewritten.downcast_ref::<Self>() else {
84+
return rewritten.optimize();
85+
};
86+
let child_type = expression.child.as_ref().type_id();
87+
// A residual expression may remain above the same child kind after a successful rewrite.
88+
// Do not immediately apply that rule again; recursively optimize only the retained child.
89+
let blocked_child_type = (child_type == previous_child_type).then_some(previous_child_type);
90+
expression.optimize_top_down(blocked_child_type)
91+
}
92+
}
93+
94+
impl Plan for ExpressionPlan {
95+
fn name(&self) -> &'static str {
96+
"ExpressionPlan"
97+
}
98+
99+
fn optimize(&self) -> VortexResult<PlanRef> {
100+
self.optimize_top_down(None)
101+
}
102+
66103
fn dtype(&self) -> &DType {
67104
self.expression.dtype()
68105
}

vortex-layout/src/plan/plans/row_idx.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,12 @@ impl PlanParentReduceRule<RowIdxPlan> for ExpressionRowIdxRule {
109109
RowIdxExpressionPartition::RowIdx => {
110110
let expression = replace_row_idx(expression.clone())?;
111111
let values = RowIdxValuesPlan::new_ref(child.row_offset, child.row_count());
112-
Ok(Some(ExpressionPlan::new(expression, values).optimize()?))
112+
Ok(Some(ExpressionPlan::new_ref(expression, values)))
113113
}
114-
RowIdxExpressionPartition::Child => Ok(Some(
115-
ExpressionPlan::new(expression.clone(), Arc::clone(&child.child)).optimize()?,
116-
)),
114+
RowIdxExpressionPartition::Child => Ok(Some(ExpressionPlan::new_ref(
115+
expression.clone(),
116+
Arc::clone(&child.child),
117+
))),
117118
};
118119
}
119120

@@ -171,18 +172,16 @@ impl PlanParentReduceRule<RowIdxPlan> for ExpressionRowIdxRule {
171172
};
172173

173174
let row_idx_expression = replace_row_idx(row_idx_expression)?;
174-
let row_idx_plan = ExpressionPlan::new(
175+
let row_idx_plan = ExpressionPlan::new_ref(
175176
row_idx_expression,
176177
RowIdxValuesPlan::new_ref(child.row_offset, child.row_count()),
177-
)
178-
.optimize()?;
179-
let child_plan =
180-
ExpressionPlan::new(child_expression, Arc::clone(&child.child)).optimize()?;
178+
);
179+
let child_plan = ExpressionPlan::new_ref(child_expression, Arc::clone(&child.child));
181180
let partitions = RowIdxPartitionPlan::try_new(row_idx_plan, child_plan)?;
182181
let residual =
183182
rewrite_partition_root(partitioned.root, partitions.dtype().clone(), &collapsed)?;
184183

185-
Ok(Some(ExpressionPlan::new(residual, partitions).optimize()?))
184+
Ok(Some(ExpressionPlan::new_ref(residual, partitions)))
186185
}
187186
}
188187

vortex-layout/src/plan/plans/struct_.rs

Lines changed: 112 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ impl PlanParentReduceRule<StructPlan> for ExpressionStructRule {
235235
.ok_or_else(|| vortex_err!("Struct field '{field_name}' has no plan"))?;
236236
let lowered = step_into_struct_field(expanded, field_name, field.dtype().clone())?;
237237

238-
return Ok(Some(ExpressionPlan::new(lowered, field).optimize()?));
238+
return Ok(Some(ExpressionPlan::new_ref(lowered, field)));
239239
}
240240

241241
let residual = partitioned.root;
@@ -283,13 +283,13 @@ impl PlanParentReduceRule<StructPlan> for ExpressionStructRule {
283283
.children
284284
.get(field_index)?
285285
.ok_or_else(|| vortex_err!("Struct field '{field_name}' has no plan"))?;
286-
let field = ExpressionPlan::new(expression, field).optimize()?;
286+
let field = ExpressionPlan::new_ref(expression, field);
287287
pruned_fields.push((field_name, field));
288288
}
289289
let rewritten: PlanRef = Arc::new(child.with_pruned_fields(pruned_fields)?);
290290
let residual = rewrite_partition_root(residual, rewritten.dtype().clone(), &collapsed)?;
291291

292-
Ok(Some(Arc::new(ExpressionPlan::new(residual, rewritten))))
292+
Ok(Some(ExpressionPlan::new_ref(residual, rewritten)))
293293
}
294294
}
295295

@@ -401,3 +401,112 @@ fn bound_pack(names: FieldNames, children: Vec<BoundExpression>) -> VortexResult
401401
children,
402402
)
403403
}
404+
405+
#[cfg(test)]
406+
mod tests {
407+
use std::sync::Arc;
408+
use std::sync::atomic::AtomicUsize;
409+
use std::sync::atomic::Ordering;
410+
411+
use vortex_array::dtype::DType;
412+
use vortex_array::dtype::Nullability;
413+
use vortex_array::dtype::PType;
414+
use vortex_array::dtype::StructFields;
415+
use vortex_array::expr::get_item;
416+
use vortex_array::expr::root;
417+
use vortex_error::VortexResult;
418+
use vortex_session::registry::ReadContext;
419+
420+
use super::StructPlan;
421+
use crate::LayoutRef;
422+
use crate::layouts::flat::FlatLayout;
423+
use crate::layouts::struct_::StructLayout;
424+
use crate::plan::ExpressionPlan;
425+
use crate::plan::LazyPlanChildren;
426+
use crate::plan::Plan;
427+
use crate::plan::PlanRef;
428+
use crate::plan::RowIdxPlan;
429+
use crate::segments::SegmentId;
430+
431+
struct CountingPlan {
432+
dtype: DType,
433+
optimizations: Arc<AtomicUsize>,
434+
}
435+
436+
impl CountingPlan {
437+
fn new_ref(dtype: DType, optimizations: Arc<AtomicUsize>) -> PlanRef {
438+
Arc::new(Self {
439+
dtype,
440+
optimizations,
441+
})
442+
}
443+
}
444+
445+
impl Plan for CountingPlan {
446+
fn optimize(&self) -> VortexResult<PlanRef> {
447+
self.optimizations.fetch_add(1, Ordering::Relaxed);
448+
Ok(Self::new_ref(
449+
self.dtype.clone(),
450+
Arc::clone(&self.optimizations),
451+
))
452+
}
453+
454+
fn dtype(&self) -> &DType {
455+
&self.dtype
456+
}
457+
458+
fn row_count(&self) -> u64 {
459+
1
460+
}
461+
}
462+
463+
fn flat(dtype: DType, segment_id: u32) -> LayoutRef {
464+
FlatLayout::new(1, dtype, SegmentId::from(segment_id), ReadContext::new([])).into_layout()
465+
}
466+
467+
#[test]
468+
fn expression_optimizes_only_referenced_struct_fields() -> VortexResult<()> {
469+
let field_dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
470+
let struct_dtype = DType::Struct(
471+
StructFields::from_iter([("a", field_dtype.clone()), ("b", field_dtype.clone())]),
472+
Nullability::NonNullable,
473+
);
474+
let layout = StructLayout::new(
475+
1,
476+
struct_dtype.clone(),
477+
vec![flat(field_dtype.clone(), 0), flat(field_dtype.clone(), 1)],
478+
);
479+
let a_optimizations = Arc::new(AtomicUsize::new(0));
480+
let b_optimizations = Arc::new(AtomicUsize::new(0));
481+
let children: Arc<[Option<PlanRef>]> = [
482+
Some(CountingPlan::new_ref(
483+
field_dtype.clone(),
484+
Arc::clone(&a_optimizations),
485+
)),
486+
Some(CountingPlan::new_ref(
487+
field_dtype,
488+
Arc::clone(&b_optimizations),
489+
)),
490+
None,
491+
]
492+
.into();
493+
let child_count = children.len();
494+
let struct_plan: PlanRef = Arc::new(StructPlan {
495+
layout,
496+
dtype: struct_dtype,
497+
children: LazyPlanChildren::new(child_count, move |index| {
498+
Ok(children.get(index).cloned().flatten())
499+
}),
500+
});
501+
let plan = RowIdxPlan::new_ref(0, struct_plan);
502+
503+
let expression = get_item("a", root())
504+
.optimize_recursive(plan.dtype())?
505+
.bind(plan.dtype())?;
506+
ExpressionPlan::new(expression, plan).optimize()?;
507+
508+
assert_eq!(a_optimizations.load(Ordering::Relaxed), 1);
509+
assert_eq!(b_optimizations.load(Ordering::Relaxed), 0);
510+
Ok(())
511+
}
512+
}

0 commit comments

Comments
 (0)