vortex_array/optimizer/
mod.rs1use smallvec::SmallVec;
20use vortex_error::VortexResult;
21use vortex_error::vortex_bail;
22use vortex_session::VortexSession;
23
24use crate::ArrayRef;
25use crate::optimizer::kernels::ArrayKernels;
26use crate::optimizer::kernels::ArrayKernelsExt;
27use crate::trace_op;
28
29pub mod kernels;
30pub mod rules;
31
32const MAX_OPTIMIZER_REWRITE_PASS: usize = 100;
35
36pub trait ArrayOptimizer {
38 fn optimize(&self) -> VortexResult<ArrayRef>;
43
44 fn optimize_ctx(&self, session: &VortexSession) -> VortexResult<ArrayRef>;
52
53 fn optimize_recursive(&self, session: &VortexSession) -> VortexResult<ArrayRef>;
58}
59
60impl ArrayOptimizer for ArrayRef {
61 fn optimize(&self) -> VortexResult<ArrayRef> {
62 Ok(try_optimize(self, None)?.unwrap_or_else(|| self.clone()))
63 }
64
65 fn optimize_ctx(&self, session: &VortexSession) -> VortexResult<ArrayRef> {
66 Ok(try_optimize(self, Some(session))?.unwrap_or_else(|| self.clone()))
67 }
68
69 fn optimize_recursive(&self, session: &VortexSession) -> VortexResult<ArrayRef> {
70 Ok(try_optimize_recursive(self, session)?.unwrap_or_else(|| self.clone()))
71 }
72}
73
74fn try_optimize(
75 array: &ArrayRef,
76 session: Option<&VortexSession>,
77) -> VortexResult<Option<ArrayRef>> {
78 let mut current_array = array.clone();
79 let mut any_optimizations = false;
80 let session_kernels = session.map(|session| session.kernels());
81
82 trace_op!(record_optimize_start(array, session.is_some()));
83
84 for _ in 0..=MAX_OPTIMIZER_REWRITE_PASS {
85 trace_op!(record_optimize_loop_start(¤t_array));
86
87 if let Some(new_array) = current_array.reduce()? {
88 current_array = new_array;
89 any_optimizations = true;
90 trace_op!(record_optimize_loop_end());
91 continue;
92 }
93
94 trace_op!(record_optimize_reduce_none(¤t_array));
95
96 let mut reduced_parent = None;
98 for (slot_idx, slot) in current_array.slots().iter().enumerate() {
99 let Some(child) = slot else {
100 continue;
101 };
102
103 if let Some(session_kernels) = &session_kernels
105 && let Some(new_array) =
106 try_session_parent_reduce(session_kernels, ¤t_array, child, slot_idx)?
107 {
108 reduced_parent = Some(new_array);
109 break;
110 }
111
112 if let Some(new_array) = child.reduce_parent(¤t_array, slot_idx)? {
113 reduced_parent = Some(new_array);
114 break;
115 }
116 }
117
118 if let Some(new_array) = reduced_parent {
119 current_array = new_array;
120 any_optimizations = true;
121 trace_op!(record_optimize_loop_end());
122 continue;
123 }
124
125 trace_op!(record_optimize_parent_reduce_none(¤t_array));
126 trace_op!(record_optimize_loop_end());
127
128 trace_op!(record_optimize_done(¤t_array, any_optimizations));
129
130 return Ok(any_optimizations.then_some(current_array));
131 }
132
133 vortex_bail!("Exceeded maximum optimization iterations (possible infinite loop)");
134}
135
136fn try_session_parent_reduce(
137 kernels: &ArrayKernels,
138 parent: &ArrayRef,
139 child: &ArrayRef,
140 slot_idx: usize,
141) -> VortexResult<Option<ArrayRef>> {
142 let Some(reduce_parent_fns) =
143 kernels.find_reduce_parent(parent.encoding_id(), child.encoding_id())
144 else {
145 return Ok(None);
146 };
147
148 #[allow(clippy::unused_enumerate_index)]
149 for (_kernel_idx, reduce_parent) in reduce_parent_fns.iter().enumerate() {
150 if let Some(new_array) = reduce_parent(child, parent, slot_idx)? {
151 trace_op!(record_session_parent_reduce_applied(
152 parent,
153 child,
154 slot_idx,
155 _kernel_idx,
156 &new_array,
157 ));
158
159 return Ok(Some(new_array));
160 }
161
162 trace_op!(record_session_parent_reduce_declined(
163 parent,
164 child,
165 slot_idx,
166 _kernel_idx,
167 ));
168 }
169
170 Ok(None)
171}
172
173fn try_optimize_recursive(
174 array: &ArrayRef,
175 session: &VortexSession,
176) -> VortexResult<Option<ArrayRef>> {
177 let mut current_array = array.clone();
178 let mut any_optimizations = false;
179
180 trace_op!(record_optimize_recursive_start(array));
181
182 if let Some(new_array) = try_optimize(¤t_array, Some(session))? {
183 current_array = new_array;
184 any_optimizations = true;
185 }
186
187 let mut new_slots = SmallVec::with_capacity(current_array.slots().len());
188 let mut any_slot_optimized = false;
189 for slot in current_array.slots() {
190 match slot {
191 Some(child) => {
192 if let Some(new_child) = try_optimize_recursive(child, session)? {
193 trace_op!(record_optimize_recursive_slot(
194 new_slots.len(),
195 child,
196 &new_child,
197 ));
198 new_slots.push(Some(new_child));
199 any_slot_optimized = true;
200 } else {
201 new_slots.push(Some(child.clone()));
202 }
203 }
204 None => new_slots.push(None),
205 }
206 }
207
208 if any_slot_optimized {
209 current_array = unsafe { current_array.with_slots(new_slots) }?;
212 any_optimizations = true;
213 }
214
215 if any_optimizations {
216 Ok(Some(current_array))
217 } else {
218 Ok(None)
219 }
220}