@@ -17,17 +17,28 @@ use vortex_compressor::scheme::DeferredEstimate;
1717use vortex_compressor:: scheme:: EstimateVerdict ;
1818use vortex_error:: VortexResult ;
1919use vortex_fastlanes:: BitPacked ;
20+ use vortex_fastlanes:: BitPackedArray ;
21+ use vortex_fastlanes:: BitPackedArrayExt ;
22+ use vortex_fastlanes:: BitPackedArraySlotsExt ;
23+ use vortex_fastlanes:: BitPackedSlots ;
2024use vortex_fastlanes:: bitpack_compress:: bit_width_histogram;
2125use vortex_fastlanes:: bitpack_compress:: bitpack_encode;
26+ use vortex_fastlanes:: bitpack_compress:: bitpack_to_best_chunk_widths;
2227use vortex_fastlanes:: bitpack_compress:: find_best_bit_width;
28+ use vortex_fastlanes:: bitpacked_v2_id;
2329
2430use crate :: ArrayAndStats ;
2531use crate :: CascadingCompressor ;
2632use crate :: CompressorContext ;
2733use crate :: Scheme ;
34+ use crate :: SchemeExt ;
2835use crate :: compress_patches;
2936
3037/// BitPacking encoding for non-negative integers.
38+ ///
39+ /// Every 1024-element chunk gets its own bit width when the compressor may use the
40+ /// `fastlanes.bitpacked_v2` format. Otherwise every chunk shares one width, which is the original
41+ /// `fastlanes.bitpacked` format.
3142#[ derive( Debug , Copy , Clone , PartialEq , Eq ) ]
3243pub struct BitPackingScheme ;
3344
@@ -66,31 +77,51 @@ impl Scheme for BitPackingScheme {
6677
6778 fn compress (
6879 & self ,
69- _compressor : & CascadingCompressor ,
80+ compressor : & CascadingCompressor ,
7081 data : & ArrayAndStats ,
71- _compress_ctx : CompressorContext ,
82+ compress_ctx : CompressorContext ,
7283 exec_ctx : & mut ExecutionCtx ,
7384 ) -> VortexResult < ArrayRef > {
7485 let primitive_array = data. array_as_primitive ( ) ;
86+ let full_width = primitive_array. ptype ( ) . bit_width ( ) ;
7587
76- let histogram = bit_width_histogram ( primitive_array, exec_ctx) ?;
77- let bw = find_best_bit_width ( primitive_array. ptype ( ) , & histogram) ?;
88+ // Per-chunk widths are the newer format of BitPacked. Produce them whenever the writer
89+ // may serialize them; otherwise every chunk shares one width, the original format.
90+ let packed = if compressor. permits_format ( bitpacked_v2_id ( ) ) {
91+ bitpack_to_best_chunk_widths ( & data. array_as_primitive ( ) . into_owned ( ) , exec_ctx) ?
92+ } else {
93+ let histogram = bit_width_histogram ( primitive_array, exec_ctx) ?;
94+ let bw = find_best_bit_width ( primitive_array. ptype ( ) , & histogram) ?;
95+ if bw as usize == full_width {
96+ return Ok ( primitive_array. array ( ) . clone ( ) ) ;
97+ }
98+ bitpack_encode (
99+ & data. array_as_primitive ( ) . into_owned ( ) ,
100+ bw,
101+ Some ( & histogram) ,
102+ exec_ctx,
103+ ) ?
104+ } ;
78105
79- // If best bw is determined to be the current bit-width, return the original array.
80- if bw as usize == primitive_array. ptype ( ) . bit_width ( ) {
106+ // If every chunk needs the full bit-width, return the original array.
107+ if packed. chunk_widths ( ) . uniform_width ( ) . map ( usize:: from) == Some ( full_width) {
108+ return Ok ( primitive_array. array ( ) . clone ( ) ) ;
109+ }
110+ // Mostly patches means the values were never really packed: the array is sparse in all
111+ // but name, and beats raw storage only by the bytes of the few values that did fit.
112+ if packed
113+ . patches ( )
114+ . is_some_and ( |p| p. num_patches ( ) * 2 >= packed. len ( ) )
115+ {
81116 return Ok ( primitive_array. array ( ) . clone ( ) ) ;
82117 }
83-
84- // Otherwise we can bitpack the array.
85- let primitive_array = primitive_array. into_owned ( ) ;
86- let packed = bitpack_encode ( & primitive_array, bw, Some ( & histogram) , exec_ctx) ?;
87118
88119 let packed_stats = packed. statistics ( ) . to_owned ( ) ;
89120 let ptype = packed. dtype ( ) . as_ptype ( ) ;
90121 let mut parts = BitPacked :: into_parts ( packed) ;
122+ let patches = parts. patches . take ( ) ;
91123
92- let array = if use_experimental_patches ( ) {
93- let patches = parts. patches . take ( ) ;
124+ if use_experimental_patches ( ) {
94125 // Transpose patches into G-ALP style PatchedArray, wrapping an inner BitPackedArray.
95126 let array = BitPacked :: try_new (
96127 parts. packed ,
@@ -100,36 +131,53 @@ impl Scheme for BitPackingScheme {
100131 parts. widths ,
101132 parts. len ,
102133 parts. offset ,
103- ) ?
104- . into_array ( ) ;
105-
106- match patches {
134+ ) ?;
135+ let array =
136+ compress_width_table ( compressor , array , & compress_ctx , exec_ctx ) ? . into_array ( ) ;
137+ return Ok ( match patches {
107138 None => array,
108139 Some ( p) => Patched :: from_array_and_patches ( array, & p, exec_ctx) ?
109140 . with_stats_set ( packed_stats)
110141 . into_array ( ) ,
111- }
112- } else {
113- // Compress patches and place back into BitPackedArray.
114- let patches = parts
115- . patches
116- . take ( )
117- . map ( |p| compress_patches ( p, exec_ctx) )
118- . transpose ( ) ?;
119- parts. patches = patches;
120- BitPacked :: try_new (
121- parts. packed ,
122- ptype,
123- parts. validity ,
124- parts. patches ,
125- parts. widths ,
126- parts. len ,
127- parts. offset ,
128- ) ?
129- . with_stats_set ( packed_stats)
130- . into_array ( )
131- } ;
142+ } ) ;
143+ }
132144
133- Ok ( array)
145+ // Compress patches and place back into BitPackedArray.
146+ let patches = patches. map ( |p| compress_patches ( p, exec_ctx) ) . transpose ( ) ?;
147+ let array = BitPacked :: try_new (
148+ parts. packed ,
149+ ptype,
150+ parts. validity ,
151+ patches,
152+ parts. widths ,
153+ parts. len ,
154+ parts. offset ,
155+ ) ?;
156+ Ok (
157+ compress_width_table ( compressor, array, & compress_ctx, exec_ctx) ?
158+ . with_stats_set ( packed_stats)
159+ . into_array ( ) ,
160+ )
134161 }
135162}
163+
164+ /// Re-encode the width table child through the compressor. Arrays whose chunks share one width
165+ /// have no table.
166+ fn compress_width_table (
167+ compressor : & CascadingCompressor ,
168+ packed : BitPackedArray ,
169+ compress_ctx : & CompressorContext ,
170+ exec_ctx : & mut ExecutionCtx ,
171+ ) -> VortexResult < BitPackedArray > {
172+ let Some ( table) = packed. width_table ( ) . cloned ( ) else {
173+ return Ok ( packed) ;
174+ } ;
175+ let compressed = compressor. compress_child (
176+ & table,
177+ compress_ctx,
178+ BitPackingScheme . id ( ) ,
179+ BitPackedSlots :: WIDTH_TABLE ,
180+ exec_ctx,
181+ ) ?;
182+ BitPacked :: with_width_table ( packed, compressed)
183+ }
0 commit comments