vortex_btrblocks/schemes/integer/
bitpacking.rs1use itertools::Itertools;
7use vortex_array::ArrayId;
8use vortex_array::ArrayRef;
9use vortex_array::Canonical;
10use vortex_array::ExecutionCtx;
11use vortex_array::IntoArray;
12use vortex_array::VTable;
13use vortex_array::arrays::Patched;
14use vortex_array::arrays::PrimitiveArray;
15use vortex_array::arrays::patched::use_experimental_patches;
16use vortex_array::arrays::primitive::PrimitiveArrayExt;
17use vortex_compressor::scheme::CompressionEstimate;
18use vortex_compressor::scheme::DeferredEstimate;
19use vortex_compressor::scheme::EstimateVerdict;
20use vortex_error::VortexResult;
21use vortex_fastlanes::BitPacked;
22use vortex_fastlanes::BitPackedArray;
23use vortex_fastlanes::BitWidths;
24use vortex_fastlanes::bitpack_compress::bit_width_histogram;
25use vortex_fastlanes::bitpack_compress::bitpack_encode;
26use vortex_fastlanes::bitpack_compress::bitpack_encode_blocked;
27use vortex_fastlanes::bitpack_compress::find_best_bit_width;
28use vortex_fastlanes::bitpack_compress::find_best_bit_widths;
29use vortex_fastlanes::bitpacked_v1_id;
30use vortex_fastlanes::bitpacked_v2_id;
31
32use crate::ArrayAndStats;
33use crate::CascadingCompressor;
34use crate::CompressorContext;
35use crate::Scheme;
36use crate::SchemeExt;
37use crate::compress_patches;
38
39#[derive(Debug, Copy, Clone, PartialEq, Eq)]
40enum BitPackingSchemeMode {
41 V1,
42 V2,
43}
44
45pub(crate) static BITPACKING_V1: BitPackingScheme = BitPackingScheme::v1();
47
48pub(crate) static BITPACKING_V2: BitPackingScheme = BitPackingScheme::v2();
50
51#[derive(Debug, Copy, Clone, PartialEq, Eq)]
60pub struct BitPackingScheme {
61 mode: BitPackingSchemeMode,
62}
63
64impl BitPackingScheme {
65 pub const fn v1() -> Self {
67 Self {
68 mode: BitPackingSchemeMode::V1,
69 }
70 }
71
72 pub const fn v2() -> Self {
74 Self {
75 mode: BitPackingSchemeMode::V2,
76 }
77 }
78}
79
80impl Default for BitPackingScheme {
81 fn default() -> Self {
82 Self::v1()
83 }
84}
85
86impl Scheme for BitPackingScheme {
87 fn scheme_name(&self) -> &'static str {
88 "vortex.int.bitpacking"
89 }
90
91 fn matches(&self, canonical: &Canonical) -> bool {
92 canonical.dtype().is_int()
93 }
94
95 fn produced_encodings(&self) -> Vec<ArrayId> {
96 let mut encodings = match self.mode {
97 BitPackingSchemeMode::V1 => vec![bitpacked_v1_id()],
99 BitPackingSchemeMode::V2 => vec![bitpacked_v1_id(), bitpacked_v2_id()],
100 };
101 if use_experimental_patches() {
102 encodings.push(Patched.id());
103 }
104 encodings
105 }
106
107 fn refine(&self, allowed: &dyn Fn(&ArrayId) -> bool) -> &dyn Scheme {
108 if allowed(&bitpacked_v2_id()) {
109 &BITPACKING_V2
110 } else {
111 &BITPACKING_V1
112 }
113 }
114
115 fn num_children(&self) -> usize {
117 match self.mode {
118 BitPackingSchemeMode::V1 => 0,
119 BitPackingSchemeMode::V2 => 1,
120 }
121 }
122
123 fn expected_compression_ratio(
124 &self,
125 data: &ArrayAndStats,
126 _compress_ctx: CompressorContext,
127 exec_ctx: &mut ExecutionCtx,
128 ) -> CompressionEstimate {
129 let stats = data.integer_stats(exec_ctx);
130
131 if stats.erased().min_is_negative() {
133 return CompressionEstimate::Verdict(EstimateVerdict::Skip);
134 }
135
136 CompressionEstimate::Deferred(DeferredEstimate::Sample)
137 }
138
139 fn compress(
140 &self,
141 compressor: &CascadingCompressor,
142 data: &ArrayAndStats,
143 compress_ctx: CompressorContext,
144 exec_ctx: &mut ExecutionCtx,
145 ) -> VortexResult<ArrayRef> {
146 let primitive_array = data.array_as_primitive().into_owned();
147 let packed = match self.mode {
148 BitPackingSchemeMode::V1 => compress_global(&primitive_array, exec_ctx)?,
149 BitPackingSchemeMode::V2 => compress_blocked(&primitive_array, exec_ctx)?,
150 };
151 let Some(packed) = packed else {
152 return Ok(primitive_array.into_array());
153 };
154
155 let packed_stats = packed.statistics().to_owned();
156 let ptype = packed.dtype().as_ptype();
157 let mut parts = BitPacked::into_parts(packed);
158
159 let patches = if use_experimental_patches() {
160 parts.patches.take()
161 } else {
162 parts.patches = parts
163 .patches
164 .take()
165 .map(|p| compress_patches(p, exec_ctx))
166 .transpose()?;
167 None
168 };
169
170 let array = match parts.bit_widths {
171 BitWidths::Global(bw) => BitPacked::try_new(
172 parts.packed,
173 ptype,
174 parts.validity,
175 parts.patches,
176 bw,
177 parts.len,
178 parts.offset,
179 )?,
180 BitWidths::Blocked(block_offsets) => {
181 let block_offsets = compressor.compress_child(
182 &block_offsets,
183 &compress_ctx,
184 self.id(),
185 0,
186 exec_ctx,
187 )?;
188 BitPacked::try_new_with_block_offsets(
189 parts.packed,
190 ptype,
191 parts.validity,
192 parts.patches,
193 block_offsets,
194 parts.len,
195 parts.offset,
196 )?
197 }
198 };
199
200 match patches {
201 None => Ok(array.with_stats_set(packed_stats).into_array()),
202 Some(p) => Ok(
203 Patched::from_array_and_patches(array.into_array(), &p, exec_ctx)?
204 .with_stats_set(packed_stats)
205 .into_array(),
206 ),
207 }
208 }
209}
210
211fn compress_global(
213 primitive_array: &PrimitiveArray,
214 exec_ctx: &mut ExecutionCtx,
215) -> VortexResult<Option<BitPackedArray>> {
216 let histogram = bit_width_histogram(primitive_array.as_view(), exec_ctx)?;
217 let bw = find_best_bit_width(primitive_array.ptype(), &histogram)?;
218 if usize::from(bw) == primitive_array.ptype().bit_width() {
219 return Ok(None);
220 }
221 bitpack_encode(primitive_array, bw, Some(&histogram), exec_ctx).map(Some)
222}
223
224fn compress_blocked(
226 primitive_array: &PrimitiveArray,
227 exec_ctx: &mut ExecutionCtx,
228) -> VortexResult<Option<BitPackedArray>> {
229 let (bit_widths, num_exceptions) = find_best_bit_widths(primitive_array, exec_ctx)?;
230 if bit_widths.is_empty() {
231 return Ok(None);
232 }
233
234 match bit_widths.iter().copied().all_equal_value() {
235 Ok(bw) if usize::from(bw) == primitive_array.ptype().bit_width() => Ok(None),
236 Ok(bw) => bitpack_encode(primitive_array, bw, None, exec_ctx).map(Some),
237 Err(_) => {
238 bitpack_encode_blocked(primitive_array, &bit_widths, Some(num_exceptions), exec_ctx)
239 .map(Some)
240 }
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use rstest::rstest;
247 use vortex_array::ArrayId;
248 use vortex_fastlanes::bitpacked_v1_id;
249 use vortex_fastlanes::bitpacked_v2_id;
250
251 use super::BITPACKING_V1;
252 use super::BITPACKING_V2;
253 use crate::Scheme;
254 use crate::SchemeExt;
255
256 #[rstest]
258 #[case::neither(false, false, false)]
259 #[case::v1(true, false, false)]
260 #[case::v2_only(false, true, true)]
261 #[case::both(true, true, true)]
262 fn refine_picks_v2_when_v2_id_is_allowed(
263 #[case] allow_v1: bool,
264 #[case] allow_v2: bool,
265 #[case] expect_v2: bool,
266 #[values(&BITPACKING_V1, &BITPACKING_V2)] scheme: &'static dyn Scheme,
267 ) {
268 let allowed = |id: &ArrayId| {
269 (allow_v1 && *id == bitpacked_v1_id()) || (allow_v2 && *id == bitpacked_v2_id())
270 };
271 let refined = scheme.refine(&allowed);
272 assert_eq!(refined.id(), scheme.id());
273 let expected: &dyn Scheme = if expect_v2 {
274 &BITPACKING_V2
275 } else {
276 &BITPACKING_V1
277 };
278 assert_eq!(refined.produced_encodings(), expected.produced_encodings());
279 }
280}