vortex_compressor/compressor/
cascade.rs1use vortex_array::ArrayRef;
7use vortex_array::Canonical;
8use vortex_array::CanonicalValidity;
9use vortex_array::ExecutionCtx;
10use vortex_array::IntoArray;
11use vortex_array::arrays::Constant;
12use vortex_array::arrays::ConstantArray;
13use vortex_array::arrays::ExtensionArray;
14use vortex_array::arrays::FixedSizeListArray;
15use vortex_array::arrays::Masked;
16use vortex_array::arrays::StructArray;
17use vortex_array::arrays::Variant;
18use vortex_array::arrays::VariantArray;
19use vortex_array::arrays::extension::ExtensionArrayExt;
20use vortex_array::arrays::fixed_size_list::FixedSizeListArrayExt;
21use vortex_array::arrays::listview::ListViewArrayExt;
22use vortex_array::arrays::listview::list_from_list_view;
23use vortex_array::arrays::masked::MaskedArraySlotsExt;
24use vortex_array::arrays::scalar_fn::AnyScalarFn;
25use vortex_array::arrays::struct_::StructArrayExt;
26use vortex_array::arrays::variant::VariantArrayExt;
27use vortex_array::scalar::Scalar;
28use vortex_error::VortexResult;
29
30use super::CascadingCompressor;
31use super::constant;
32use crate::scheme::CompressorContext;
33use crate::scheme::Scheme;
34use crate::scheme::SchemeExt;
35use crate::scheme::SchemeId;
36use crate::stats::ArrayAndStats;
37use crate::stats::GenerateStatsOptions;
38use crate::trace;
39
40impl CascadingCompressor {
41 pub fn compress(
49 &self,
50 array: &ArrayRef,
51 exec_ctx: &mut ExecutionCtx,
52 ) -> VortexResult<ArrayRef> {
53 let before_nbytes = array.nbytes();
54 let span = trace::compress_span(array.len(), array.dtype(), before_nbytes);
55 let _enter = span.enter();
56
57 let canonical = array.clone().execute::<CanonicalValidity>(exec_ctx)?.0;
58 let compact = canonical.compact(exec_ctx)?;
59 let compressed = self.compress_canonical(compact, CompressorContext::new(), exec_ctx)?;
60
61 trace::record_compress_outcome(&span, before_nbytes, compressed.nbytes());
62
63 Ok(compressed)
64 }
65
66 pub fn compress_child(
76 &self,
77 child: &ArrayRef,
78 parent_ctx: &CompressorContext,
79 parent_id: SchemeId,
80 child_index: usize,
81 exec_ctx: &mut ExecutionCtx,
82 ) -> VortexResult<ArrayRef> {
83 if parent_ctx.finished_cascading() {
84 trace::cascade_exhausted(parent_id, child_index);
85 return Ok(child.clone());
86 }
87
88 let canonical = child.clone().execute::<CanonicalValidity>(exec_ctx)?.0;
89 let compact = canonical.compact(exec_ctx)?;
90
91 let child_ctx = parent_ctx
92 .clone()
93 .descend_with_scheme(parent_id, child_index);
94 self.compress_canonical(compact, child_ctx, exec_ctx)
95 }
96
97 pub(super) fn compress_canonical(
103 &self,
104 array: Canonical,
105 compress_ctx: CompressorContext,
106 exec_ctx: &mut ExecutionCtx,
107 ) -> VortexResult<ArrayRef> {
108 match array {
109 Canonical::Null(null_array) => Ok(null_array.into_array()),
110 Canonical::Bool(bool_array) => {
111 self.choose_and_compress(Canonical::Bool(bool_array), compress_ctx, exec_ctx)
112 }
113 Canonical::Primitive(primitive) => {
114 self.choose_and_compress(Canonical::Primitive(primitive), compress_ctx, exec_ctx)
115 }
116 Canonical::Decimal(decimal) => {
117 self.choose_and_compress(Canonical::Decimal(decimal), compress_ctx, exec_ctx)
118 }
119 Canonical::Struct(struct_array) => {
120 let fields = struct_array
121 .iter_unmasked_fields()
122 .map(|field| self.compress(field, exec_ctx))
123 .collect::<Result<Vec<_>, _>>()?;
124
125 Ok(StructArray::try_new(
126 struct_array.names().clone(),
127 fields,
128 struct_array.len(),
129 struct_array.validity()?,
130 )?
131 .into_array())
132 }
133 Canonical::Union(_) => {
134 todo!("TODO(connor)[Union]: implement compression for Union arrays")
135 }
136 Canonical::List(list_view_array) => {
137 if list_view_array.is_zero_copy_to_list() || list_view_array.elements().is_empty() {
138 let list_array = list_from_list_view(list_view_array, exec_ctx)?;
139 self.compress_list_array(list_array, compress_ctx, exec_ctx)
140 } else {
141 self.compress_list_view_array(list_view_array, compress_ctx, exec_ctx)
142 }
143 }
144 Canonical::FixedSizeList(fsl_array) => {
145 let compressed_elems = self.compress(fsl_array.elements(), exec_ctx)?;
146
147 Ok(FixedSizeListArray::try_new(
148 compressed_elems,
149 fsl_array.list_size(),
150 fsl_array.validity()?,
151 fsl_array.len(),
152 )?
153 .into_array())
154 }
155 Canonical::VarBinView(varbinview) => {
156 self.choose_and_compress(Canonical::VarBinView(varbinview), compress_ctx, exec_ctx)
157 }
158 Canonical::Extension(ext_array) => {
159 let scheme_compressed = self.choose_and_compress(
161 Canonical::Extension(ext_array.clone()),
162 compress_ctx,
163 exec_ctx,
164 )?;
165 if scheme_compressed.is::<AnyScalarFn>() {
167 return Ok(scheme_compressed);
168 }
169
170 if scheme_compressed.is::<Constant>() {
173 return Ok(scheme_compressed);
174 }
175 if let Some(masked) = scheme_compressed.as_opt::<Masked>()
176 && masked.child().is::<Constant>()
177 {
178 return Ok(scheme_compressed);
179 }
180
181 let compressed_storage = self.compress(ext_array.storage_array(), exec_ctx)?;
184 let storage_compressed =
185 ExtensionArray::new(ext_array.ext_dtype().clone(), compressed_storage)
186 .into_array();
187
188 if scheme_compressed.nbytes() < storage_compressed.nbytes() {
189 Ok(scheme_compressed)
190 } else {
191 Ok(storage_compressed)
192 }
193 }
194 Canonical::Variant(variant_array) => {
195 let core_storage =
196 self.compress_physical_slots(variant_array.core_storage(), exec_ctx)?;
197 let shredded = variant_array
198 .shredded()
199 .map(|arr| {
200 if arr.is::<Variant>() {
202 self.compress_physical_slots(arr, exec_ctx)
203 } else {
204 self.compress(arr, exec_ctx)
205 }
206 })
207 .transpose()?;
208
209 Ok(VariantArray::try_new(core_storage, shredded)?.into_array())
210 }
211 }
212 }
213
214 fn choose_and_compress(
229 &self,
230 canonical: Canonical,
231 compress_ctx: CompressorContext,
232 exec_ctx: &mut ExecutionCtx,
233 ) -> VortexResult<ArrayRef> {
234 let eligible_schemes: Vec<&'static dyn Scheme> = self
235 .schemes
236 .iter()
237 .copied()
238 .filter(|s| s.matches(&canonical) && !self.is_excluded(*s, &compress_ctx))
239 .collect();
240
241 let array: ArrayRef = canonical.into();
242
243 if array.is_empty() {
244 return Ok(array);
245 }
246
247 if array.all_invalid(exec_ctx)? {
248 return Ok(
249 ConstantArray::new(Scalar::null(array.dtype().clone()), array.len()).into_array(),
250 );
251 }
252
253 let before_nbytes = array.nbytes();
254
255 let merged_opts = eligible_schemes
256 .iter()
257 .fold(GenerateStatsOptions::default(), |acc, s| {
258 acc.merge(s.stats_options())
259 });
260 let compress_ctx = compress_ctx.with_merged_stats_options(merged_opts);
261
262 let data = ArrayAndStats::new(array, merged_opts);
263
264 if !compress_ctx.is_sample() && constant::is_constant_for_compression(&data, exec_ctx)? {
268 let _winner_span =
269 trace::winner_compress_span(constant::CONSTANT_SCHEME_ID, before_nbytes).entered();
270 let compressed = constant::compress_constant(data.array(), exec_ctx)?;
271
272 let after_nbytes = compressed.nbytes();
273 let actual_ratio =
274 (after_nbytes != 0).then(|| before_nbytes as f64 / after_nbytes as f64);
275 let accepted = after_nbytes < before_nbytes;
276 trace::record_winner_compress_result(after_nbytes, None, actual_ratio, accepted);
277
278 return if accepted {
279 Ok(compressed)
280 } else {
281 Ok(data.into_array())
282 };
283 }
284
285 if eligible_schemes.is_empty() {
286 return Ok(data.into_array());
287 }
288
289 let Some((winner, winner_estimate)) =
290 self.choose_best_scheme(&eligible_schemes, &data, compress_ctx.clone(), exec_ctx)?
291 else {
292 return Ok(data.into_array());
293 };
294
295 let error_ctx = trace::enabled_error_context(&compress_ctx);
298 let _winner_span = trace::winner_compress_span(winner.id(), before_nbytes).entered();
299 let compressed = winner
300 .compress(self, &data, compress_ctx, exec_ctx)
301 .inspect_err(|err| {
302 trace::scheme_compress_failed(winner.id(), before_nbytes, error_ctx.as_ref(), err);
306 })?;
307
308 let after_nbytes = compressed.nbytes();
309 let actual_ratio = (after_nbytes != 0).then(|| before_nbytes as f64 / after_nbytes as f64);
310
311 let accepted = after_nbytes < before_nbytes || compressed.is::<AnyScalarFn>();
313
314 trace::record_winner_compress_result(
315 after_nbytes,
316 winner_estimate.trace_ratio(),
317 actual_ratio,
318 accepted,
319 );
320
321 if accepted {
322 Ok(compressed)
323 } else {
324 Ok(data.into_array())
325 }
326 }
327}