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