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::aggregate_fn::fns::uncompressed_size_in_bytes::UncompressedSizeInBytes;
12use vortex_array::aggregate_fn::fns::uncompressed_size_in_bytes::UncompressedSizeOpts;
13use vortex_array::aggregate_fn::fns::uncompressed_size_in_bytes::uncompressed_size_in_bytes;
14use vortex_array::arrays::Constant;
15use vortex_array::arrays::ConstantArray;
16use vortex_array::arrays::ExtensionArray;
17use vortex_array::arrays::FixedSizeListArray;
18use vortex_array::arrays::Masked;
19use vortex_array::arrays::StructArray;
20use vortex_array::arrays::UnionArray;
21use vortex_array::arrays::Variant;
22use vortex_array::arrays::VariantArray;
23use vortex_array::arrays::extension::ExtensionArrayExt;
24use vortex_array::arrays::fixed_size_list::FixedSizeListArrayExt;
25use vortex_array::arrays::fixed_size_list::FixedSizeListArraySlotsExt;
26use vortex_array::arrays::listview::ListViewArraySlotsExt;
27use vortex_array::arrays::listview::list_from_list_view;
28use vortex_array::arrays::masked::MaskedArraySlotsExt;
29use vortex_array::arrays::struct_::StructArrayExt;
30use vortex_array::arrays::union::UnionArrayExt;
31use vortex_array::arrays::union::UnionArraySlotsExt;
32use vortex_array::arrays::variant::VariantArraySlotsExt;
33use vortex_array::scalar::Scalar;
34use vortex_error::VortexResult;
35use vortex_error::vortex_err;
36
37use super::CascadingCompressor;
38use super::constant;
39use crate::scheme::CompressorContext;
40use crate::scheme::Scheme;
41use crate::scheme::SchemeExt;
42use crate::scheme::SchemeId;
43use crate::stats::ArrayAndStats;
44use crate::stats::GenerateStatsOptions;
45use crate::trace;
46
47enum Selection {
49 Compressed(ArrayRef),
51 Unchanged(ArrayRef),
53}
54
55impl Selection {
56 fn into_array(self) -> ArrayRef {
58 match self {
59 Selection::Compressed(array) | Selection::Unchanged(array) => array,
60 }
61 }
62}
63
64impl CascadingCompressor {
65 pub fn compress(
73 &self,
74 array: &ArrayRef,
75 exec_ctx: &mut ExecutionCtx,
76 ) -> VortexResult<ArrayRef> {
77 let before_nbytes = exact_uncompressed_size(array, exec_ctx)?;
78 let span = trace::compress_span(array.len(), array.dtype(), before_nbytes);
79 let _enter = span.enter();
80
81 let canonical = array.clone().execute::<CanonicalValidity>(exec_ctx)?.0;
82 let compact = canonical.compact(exec_ctx)?;
83 let compressed = self.compress_canonical(compact, CompressorContext::new(), exec_ctx)?;
84
85 trace::record_compress_outcome(&span, before_nbytes, compressed.nbytes());
86
87 Ok(compressed)
88 }
89
90 pub fn compress_child(
100 &self,
101 child: &ArrayRef,
102 parent_ctx: &CompressorContext,
103 parent_id: SchemeId,
104 child_index: usize,
105 exec_ctx: &mut ExecutionCtx,
106 ) -> VortexResult<ArrayRef> {
107 if parent_ctx.finished_cascading() {
108 trace::cascade_exhausted(parent_id, child_index);
109 return Ok(child.clone());
110 }
111
112 let canonical = child.clone().execute::<CanonicalValidity>(exec_ctx)?.0;
113 let compact = canonical.compact(exec_ctx)?;
114
115 let child_ctx = parent_ctx
116 .clone()
117 .descend_with_scheme(parent_id, child_index);
118 self.compress_canonical(compact, child_ctx, exec_ctx)
119 }
120
121 pub(super) fn compress_canonical(
127 &self,
128 array: Canonical,
129 compress_ctx: CompressorContext,
130 exec_ctx: &mut ExecutionCtx,
131 ) -> VortexResult<ArrayRef> {
132 match array {
133 Canonical::Null(null_array) => Ok(null_array.into_array()),
134 Canonical::Bool(bool_array) => self
135 .choose_and_compress(Canonical::Bool(bool_array), compress_ctx, exec_ctx)
136 .map(Selection::into_array),
137 Canonical::Primitive(primitive) => self
138 .choose_and_compress(Canonical::Primitive(primitive), compress_ctx, exec_ctx)
139 .map(Selection::into_array),
140 Canonical::Decimal(decimal) => self
141 .choose_and_compress(Canonical::Decimal(decimal), compress_ctx, exec_ctx)
142 .map(Selection::into_array),
143 Canonical::Struct(struct_array) => {
144 let fields = struct_array
145 .iter_unmasked_fields()
146 .map(|field| self.compress(field, exec_ctx))
147 .collect::<Result<Vec<_>, _>>()?;
148
149 Ok(StructArray::try_new(
150 struct_array.names().clone(),
151 fields,
152 struct_array.len(),
153 struct_array.validity()?,
154 )?
155 .into_array())
156 }
157 Canonical::Union(union_array) => {
158 let type_ids = self.compress(union_array.type_ids(), exec_ctx)?;
159 let children = union_array
160 .iter_children()
161 .map(|child| self.compress(child, exec_ctx))
162 .collect::<Result<Vec<_>, _>>()?;
163
164 Ok(
165 UnionArray::try_new(type_ids, union_array.variants().clone(), children)?
166 .into_array(),
167 )
168 }
169 Canonical::List(list_view_array) => {
170 if list_view_array.is_zero_copy_to_list() || list_view_array.elements().is_empty() {
171 let list_array = list_from_list_view(list_view_array, exec_ctx)?;
172 self.compress_list_array(list_array, compress_ctx, exec_ctx)
173 } else {
174 self.compress_list_view_array(list_view_array, compress_ctx, exec_ctx)
175 }
176 }
177 Canonical::Map(map_array) => self.compress_map_array(map_array, compress_ctx, exec_ctx),
178 Canonical::FixedSizeList(fsl_array) => {
179 let compressed_elems = self.compress(fsl_array.elements(), exec_ctx)?;
180
181 Ok(FixedSizeListArray::try_new(
182 compressed_elems,
183 fsl_array.list_size(),
184 fsl_array.validity()?,
185 fsl_array.len(),
186 )?
187 .into_array())
188 }
189 Canonical::VarBinView(varbinview) => self
190 .choose_and_compress(Canonical::VarBinView(varbinview), compress_ctx, exec_ctx)
191 .map(Selection::into_array),
192 Canonical::Extension(ext_array) => {
193 let selection = self.choose_and_compress(
195 Canonical::Extension(ext_array.clone()),
196 compress_ctx,
197 exec_ctx,
198 )?;
199
200 if let Selection::Compressed(compressed) = &selection
203 && (compressed.is::<Constant>()
204 || compressed
205 .as_opt::<Masked>()
206 .is_some_and(|masked| masked.child().is::<Constant>()))
207 {
208 return Ok(selection.into_array());
209 }
210
211 let compressed_storage = self.compress(ext_array.storage_array(), exec_ctx)?;
214 let storage_compressed =
215 ExtensionArray::new(ext_array.ext_dtype().clone(), compressed_storage)
216 .into_array();
217
218 match selection {
219 Selection::Compressed(scheme_compressed)
220 if scheme_compressed.nbytes() < storage_compressed.nbytes() =>
221 {
222 Ok(scheme_compressed)
223 }
224 Selection::Compressed(_) | Selection::Unchanged(_) => Ok(storage_compressed),
227 }
228 }
229 Canonical::Variant(variant_array) => {
230 let core_storage =
231 self.compress_physical_slots(variant_array.core_storage(), exec_ctx)?;
232 let shredded = variant_array
233 .shredded()
234 .map(|arr| {
235 if arr.is::<Variant>() {
237 self.compress_physical_slots(arr, exec_ctx)
238 } else {
239 self.compress(arr, exec_ctx)
240 }
241 })
242 .transpose()?;
243
244 Ok(VariantArray::try_new(core_storage, shredded)?.into_array())
245 }
246 }
247 }
248
249 fn choose_and_compress(
267 &self,
268 canonical: Canonical,
269 compress_ctx: CompressorContext,
270 exec_ctx: &mut ExecutionCtx,
271 ) -> VortexResult<Selection> {
272 let eligible_schemes: Vec<&'static dyn Scheme> = self
273 .schemes
274 .iter()
275 .copied()
276 .filter(|s| s.matches(&canonical) && !self.is_excluded(*s, &compress_ctx))
277 .collect();
278
279 let array: ArrayRef = canonical.into();
280
281 if array.is_empty() {
282 return Ok(Selection::Unchanged(array));
283 }
284
285 if array.all_invalid(exec_ctx)? {
286 return Ok(Selection::Compressed(
287 ConstantArray::new(Scalar::null(array.dtype().clone()), array.len()).into_array(),
288 ));
289 }
290
291 if !compress_ctx.is_sample() && constant::is_constant_for_compression(&array, exec_ctx)? {
297 return constant::compress_as_constant(array, exec_ctx).map(Selection::Compressed);
298 }
299
300 let before_nbytes = exact_uncompressed_size(&array, exec_ctx)?;
302
303 let merged_opts = eligible_schemes
304 .iter()
305 .fold(GenerateStatsOptions::default(), |acc, s| {
306 acc.merge(s.stats_options())
307 });
308 let compress_ctx = compress_ctx.with_merged_stats_options(merged_opts);
309
310 let data = ArrayAndStats::new(array, merged_opts);
311
312 if eligible_schemes.is_empty() {
313 return Ok(Selection::Unchanged(data.into_array()));
314 }
315
316 let Some((winner, winner_estimate)) =
317 self.choose_best_scheme(&eligible_schemes, &data, compress_ctx.clone(), exec_ctx)?
318 else {
319 return Ok(Selection::Unchanged(data.into_array()));
320 };
321
322 let error_ctx = trace::enabled_error_context(&compress_ctx);
325 let _winner_span = trace::winner_compress_span(winner.id(), before_nbytes).entered();
326 let compressed = winner
327 .compress(self, &data, compress_ctx, exec_ctx)
328 .inspect_err(|err| {
329 trace::scheme_compress_failed(winner.id(), before_nbytes, error_ctx.as_ref(), err);
333 })?;
334
335 let after_nbytes = compressed.nbytes();
336 let actual_ratio = (after_nbytes != 0).then(|| before_nbytes as f64 / after_nbytes as f64);
337
338 let accepted = after_nbytes < before_nbytes;
339
340 trace::record_winner_compress_result(
341 after_nbytes,
342 winner_estimate.trace_ratio(),
343 actual_ratio,
344 accepted,
345 );
346
347 if accepted {
348 Ok(Selection::Compressed(compressed))
349 } else {
350 Ok(Selection::Unchanged(data.into_array()))
351 }
352 }
353}
354
355pub(super) fn exact_uncompressed_size(
359 array: &ArrayRef,
360 exec_ctx: &mut ExecutionCtx,
361) -> VortexResult<u64> {
362 if !UncompressedSizeInBytes::supports_dtype(array.dtype()) {
363 return Ok(array.nbytes());
364 }
365 let size = uncompressed_size_in_bytes(array, UncompressedSizeOpts::exact(), exec_ctx)?;
366 u64::try_from(size).map_err(|e| vortex_err!("uncompressed size does not fit in u64: {e}"))
367}