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(optimize_owned(self.clone(), None)?.0)
63 }
64
65 fn optimize_ctx(&self, session: &VortexSession) -> VortexResult<ArrayRef> {
66 Ok(optimize_owned(self.clone(), Some(session))?.0)
67 }
68
69 fn optimize_recursive(&self, session: &VortexSession) -> VortexResult<ArrayRef> {
70 Ok(try_optimize_recursive(self.clone(), session)?.0)
71 }
72}
73
74fn optimize_owned(
75 mut current_array: ArrayRef,
76 session: Option<&VortexSession>,
77) -> VortexResult<(ArrayRef, bool)> {
78 let mut any_optimizations = false;
79 let session_kernels = session.map(|session| session.kernels());
80
81 trace_op!(record_optimize_start(¤t_array, session.is_some()));
82
83 for _ in 0..=MAX_OPTIMIZER_REWRITE_PASS {
84 trace_op!(record_optimize_loop_start(¤t_array));
85
86 if let Some(new_array) = current_array.reduce()? {
87 current_array = new_array;
88 any_optimizations = true;
89 trace_op!(record_optimize_loop_end());
90 continue;
91 }
92
93 trace_op!(record_optimize_reduce_none(¤t_array));
94
95 let mut reduced_parent = None;
97 for (slot_idx, slot) in current_array.slots().iter().enumerate() {
98 let Some(child) = slot else {
99 continue;
100 };
101
102 if let Some(session_kernels) = &session_kernels
104 && let Some(new_array) =
105 try_session_parent_reduce(session_kernels, ¤t_array, child, slot_idx)?
106 {
107 reduced_parent = Some(new_array);
108 break;
109 }
110
111 if let Some(new_array) = child.reduce_parent(¤t_array, slot_idx)? {
112 reduced_parent = Some(new_array);
113 break;
114 }
115 }
116
117 if let Some(new_array) = reduced_parent {
118 current_array = new_array;
119 any_optimizations = true;
120 trace_op!(record_optimize_loop_end());
121 continue;
122 }
123
124 trace_op!(record_optimize_parent_reduce_none(¤t_array));
125 trace_op!(record_optimize_loop_end());
126
127 trace_op!(record_optimize_done(¤t_array, any_optimizations));
128
129 return Ok((current_array, any_optimizations));
130 }
131
132 vortex_bail!("Exceeded maximum optimization iterations (possible infinite loop)");
133}
134
135fn try_session_parent_reduce(
136 kernels: &ArrayKernels,
137 parent: &ArrayRef,
138 child: &ArrayRef,
139 slot_idx: usize,
140) -> VortexResult<Option<ArrayRef>> {
141 let Some(reduce_parent_fns) =
142 kernels.find_reduce_parent(parent.encoding_id(), child.encoding_id())
143 else {
144 return Ok(None);
145 };
146
147 #[allow(clippy::unused_enumerate_index)]
148 for (_kernel_idx, reduce_parent) in reduce_parent_fns.iter().enumerate() {
149 if let Some(new_array) = reduce_parent(child, parent, slot_idx)? {
150 trace_op!(record_session_parent_reduce_applied(
151 parent,
152 child,
153 slot_idx,
154 _kernel_idx,
155 &new_array,
156 ));
157
158 return Ok(Some(new_array));
159 }
160
161 trace_op!(record_session_parent_reduce_declined(
162 parent,
163 child,
164 slot_idx,
165 _kernel_idx,
166 ));
167 }
168
169 Ok(None)
170}
171
172fn try_optimize_recursive(
173 current_array: ArrayRef,
174 session: &VortexSession,
175) -> VortexResult<(ArrayRef, bool)> {
176 trace_op!(record_optimize_recursive_start(¤t_array));
177
178 let (mut current_array, mut any_optimizations) = optimize_owned(current_array, Some(session))?;
179
180 let mut new_slots = SmallVec::with_capacity(current_array.slots().len());
181 let mut any_slot_optimized = false;
182 for slot in current_array.slots() {
183 match slot {
184 Some(child) => {
185 let (new_child, new_slot_optimized) =
186 try_optimize_recursive(child.clone(), session)?;
187 if new_slot_optimized {
188 trace_op!(record_optimize_recursive_slot(
189 new_slots.len(),
190 child,
191 &new_child,
192 ));
193 }
194 new_slots.push(Some(new_child));
195 any_slot_optimized |= new_slot_optimized;
196 }
197 None => new_slots.push(None),
198 }
199 }
200
201 if any_slot_optimized {
202 current_array = unsafe { current_array.with_slots(new_slots) }?;
205 any_optimizations = true;
206 }
207
208 Ok((current_array, any_optimizations))
209}