Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
pola-rs
GitHub Repository: pola-rs/polars
Path: blob/main/crates/polars-plan/src/plans/conversion/stack_opt.rs
6940 views
1
use std::borrow::Borrow;
2
3
use self::type_check::TypeCheckRule;
4
use super::*;
5
6
/// Applies expression simplification and type coercion during conversion to IR.
7
pub struct ConversionOptimizer {
8
scratch: Vec<(Node, usize)>,
9
schemas: Vec<Schema>,
10
11
simplify: Option<SimplifyExprRule>,
12
coerce: Option<TypeCoercionRule>,
13
check: Option<TypeCheckRule>,
14
// IR's can be cached in the DSL.
15
// But if they are used multiple times in DSL (e.g. concat/join)
16
// then it can occur that we take a slot multiple times.
17
// So we keep track of the arena versions used and allow only
18
// one unique IR cache to be reused.
19
pub(super) used_arenas: PlHashSet<u32>,
20
}
21
22
struct ExtendVec<'a> {
23
out: &'a mut Vec<(Node, usize)>,
24
schema_idx: usize,
25
}
26
impl Extend<Node> for ExtendVec<'_> {
27
fn extend<T: IntoIterator<Item = Node>>(&mut self, iter: T) {
28
self.out
29
.extend(iter.into_iter().map(|n| (n, self.schema_idx)))
30
}
31
}
32
33
impl ConversionOptimizer {
34
pub fn new(simplify: bool, type_coercion: bool, type_check: bool) -> Self {
35
let simplify = if simplify {
36
Some(SimplifyExprRule {})
37
} else {
38
None
39
};
40
41
let coerce = if type_coercion {
42
Some(TypeCoercionRule {})
43
} else {
44
None
45
};
46
47
let check = if type_check {
48
Some(TypeCheckRule)
49
} else {
50
None
51
};
52
53
ConversionOptimizer {
54
scratch: Vec::with_capacity(8),
55
schemas: Vec::new(),
56
simplify,
57
coerce,
58
check,
59
used_arenas: Default::default(),
60
}
61
}
62
63
pub fn push_scratch(&mut self, expr: Node, expr_arena: &Arena<AExpr>) {
64
self.scratch.push((expr, 0));
65
// traverse all subexpressions and add to the stack
66
let expr = unsafe { expr_arena.get_unchecked(expr) };
67
expr.inputs_rev(&mut ExtendVec {
68
out: &mut self.scratch,
69
schema_idx: 0,
70
});
71
}
72
73
pub fn fill_scratch<I, N>(&mut self, exprs: I, expr_arena: &Arena<AExpr>)
74
where
75
I: IntoIterator<Item = N>,
76
N: Borrow<Node>,
77
{
78
for e in exprs {
79
let node = *e.borrow();
80
self.push_scratch(node, expr_arena);
81
}
82
}
83
84
/// Optimizes the expressions in the scratch space. This should be called after filling the
85
/// scratch space with the expressions that you want to optimize.
86
pub fn optimize_exprs(
87
&mut self,
88
expr_arena: &mut Arena<AExpr>,
89
ir_arena: &mut Arena<IR>,
90
current_ir_node: Node,
91
// Use the schema of `current_ir_node` instead of its input when resolving expr fields.
92
use_current_node_schema: bool,
93
) -> PolarsResult<()> {
94
// Different from the stack-opt in the optimizer phase, this does a single pass until fixed point per expression.
95
96
if let Some(rule) = &mut self.check {
97
while let Some(x) = rule.optimize_plan(ir_arena, expr_arena, current_ir_node)? {
98
ir_arena.replace(current_ir_node, x);
99
}
100
}
101
102
// process the expressions on the stack and apply optimizations.
103
let schema = if use_current_node_schema {
104
ir_arena.get(current_ir_node).schema(ir_arena)
105
} else {
106
get_input_schema(ir_arena, current_ir_node)
107
};
108
let plan = ir_arena.get(current_ir_node);
109
let mut ctx = OptimizeExprContext {
110
in_filter: matches!(plan, IR::Filter { .. }),
111
has_inputs: !get_input(ir_arena, current_ir_node).is_empty(),
112
..Default::default()
113
};
114
#[cfg(feature = "python")]
115
{
116
use crate::dsl::python_dsl::PythonScanSource;
117
ctx.in_pyarrow_scan = matches!(plan, IR::PythonScan { options } if options.python_source == PythonScanSource::Pyarrow);
118
ctx.in_io_plugin = matches!(plan, IR::PythonScan { options } if options.python_source == PythonScanSource::IOPlugin);
119
};
120
121
self.schemas.clear();
122
while let Some((current_expr_node, schema_idx)) = self.scratch.pop() {
123
let expr = unsafe { expr_arena.get_unchecked(current_expr_node) };
124
125
if expr.is_leaf() {
126
continue;
127
}
128
129
// Evaluation expressions still need to do rules on the evaluation expression but the
130
// schema is not the same and it is not concluded in the inputs. Therefore, we handl
131
if let AExpr::Eval {
132
expr,
133
evaluation,
134
variant,
135
} = expr
136
{
137
let schema = if schema_idx == 0 {
138
&schema
139
} else {
140
&self.schemas[schema_idx - 1]
141
};
142
let expr = expr_arena.get(*expr).get_dtype(schema, expr_arena)?;
143
144
let element_dtype = variant.element_dtype(&expr)?;
145
let schema = Schema::from_iter([(PlSmallStr::EMPTY, element_dtype.clone())]);
146
self.schemas.push(schema);
147
self.scratch.push((*evaluation, self.schemas.len()));
148
}
149
150
let schema = if schema_idx == 0 {
151
&schema
152
} else {
153
&self.schemas[schema_idx - 1]
154
};
155
156
if let Some(rule) = &mut self.simplify {
157
while let Some(x) =
158
rule.optimize_expr(expr_arena, current_expr_node, schema, ctx)?
159
{
160
expr_arena.replace(current_expr_node, x);
161
}
162
}
163
if let Some(rule) = &mut self.coerce {
164
while let Some(x) =
165
rule.optimize_expr(expr_arena, current_expr_node, schema, ctx)?
166
{
167
expr_arena.replace(current_expr_node, x);
168
}
169
}
170
171
let expr = unsafe { expr_arena.get_unchecked(current_expr_node) };
172
// traverse subexpressions and add to the stack
173
expr.inputs_rev(&mut ExtendVec {
174
out: &mut self.scratch,
175
schema_idx,
176
});
177
}
178
179
Ok(())
180
}
181
}
182
183