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