bitpacking/
lib.rs

1// We could auto-include the README here to avoid duplicating documentation,
2//   #![doc = include_str!("../README.md")]
3// but at the moment that seems a bit too long(?),
4// so for now only do it during testing to validate README's examples.
5#![cfg_attr(doctest, doc = include_str!("../README.md"))]
6
7/*!  # Fast Bitpacking algorithms
8
9This crate is a **Rust port of [Daniel Lemire's simdcomp C library](https://github.com/lemire/simdcomp)**.
10It contains different flavor of integers compression via bitpacking :  `BitPacker1x`, `BitPacker4x`, and `BitPacker8x`.
11
12Each produces different formats, and are incompatible one with another,
13and requires integers to be encoded in block of different size..
14
15`BitPacker4x` and `BitPacker8x` are designed specifically to leverage `SSE3`
16and `AVX2` instructions respectively.
17
18The library will fall back to a scalar implementation if these instruction
19sets are not available. For instance :
20- because your compilation target architecture is not `x86_64`
21- because the CPU you use is from an older generation
22
23I recommend using `BitPacker4x` if you are in doubt.
24
25See the [`BitPacker` trait](./trait.BitPacker.html) for example usage.
26
27*/
28
29#![allow(unused_unsafe)]
30#![warn(missing_docs)]
31
32use std::marker::Sized;
33
34#[cfg(test)]
35#[macro_use]
36pub(crate) mod tests;
37
38#[macro_use]
39mod macros;
40#[macro_use]
41mod macros_simple;
42
43trait Available {
44    fn available() -> bool;
45}
46
47trait UnsafeBitPacker {
48    const BLOCK_LEN: usize;
49    unsafe fn compress(decompressed: &[u32], compressed: &mut [u8], num_bits: u8) -> usize;
50    unsafe fn compress_sorted(
51        initial: u32,
52        decompressed: &[u32],
53        compressed: &mut [u8],
54        num_bits: u8,
55    ) -> usize;
56    unsafe fn compress_strictly_sorted(
57        initial: Option<u32>,
58        decompressed: &[u32],
59        compressed: &mut [u8],
60        num_bits: u8,
61    ) -> usize;
62    unsafe fn decompress(compressed: &[u8], decompressed: &mut [u32], num_bits: u8) -> usize;
63    unsafe fn decompress_sorted(
64        initial: u32,
65        compressed: &[u8],
66        decompressed: &mut [u32],
67        num_bits: u8,
68    ) -> usize;
69    unsafe fn decompress_strictly_sorted(
70        initial: Option<u32>,
71        compressed: &[u8],
72        decompressed: &mut [u32],
73        num_bits: u8,
74    ) -> usize;
75    unsafe fn num_bits(decompressed: &[u32]) -> u8;
76    unsafe fn num_bits_sorted(initial: u32, decompressed: &[u32]) -> u8;
77    unsafe fn num_bits_strictly_sorted(initial: Option<u32>, decompressed: &[u32]) -> u8;
78}
79
80/// # Examples without delta-encoding
81/// ```
82/// use bitpacking::{BitPacker4x, BitPacker};
83///
84/// # fn main() {
85/// # let my_data: Vec<u32> = vec![7, 7, 7, 7, 11, 10, 15, 13, 6, 5, 3, 14, 5, 7,
86/// #    15, 12, 1, 10, 8, 10, 12, 14, 13, 1, 10, 1, 1, 10, 4, 15, 12,
87/// #    1, 2, 0, 8, 5, 14, 5, 2, 4, 1, 6, 14, 13, 5, 10, 10, 1, 6, 4,
88/// #    1, 12, 1, 1, 5, 15, 15, 2, 8, 6, 4, 3, 10, 8, 8, 9, 2, 6, 10,
89/// #    5, 7, 9, 0, 13, 15, 5, 13, 10, 0, 2, 10, 14, 5, 9, 12, 8, 5, 10,
90/// #    8, 8, 10, 5, 13, 8, 11, 14, 7, 14, 4, 2, 9, 12, 14, 5, 15, 12, 0,
91/// #    12, 13, 3, 13, 5, 4, 15, 9, 8, 9, 3, 3, 3, 1, 12, 0, 6, 11, 11, 12, 4];
92///
93/// // Detects if `SSE3` is available on the current computed
94/// // and uses the best available implementation accordingly.
95/// let bitpacker = BitPacker4x::new();
96///
97/// // Computes the number of bits used for each integer in the blocks.
98/// // my_data is assumed to have a len of 128 for `BitPacker4x`.
99/// let num_bits: u8 = bitpacker.num_bits(&my_data);
100/// # assert_eq!(num_bits, 4);
101///
102/// // The compressed array will take exactly `num_bits * BitPacker4x::BLOCK_LEN / 8`.
103/// // But it is ok to have an output with a different len as long as it is larger
104/// // than this.
105/// let mut compressed = vec![0u8; 4 * BitPacker4x::BLOCK_LEN];
106///
107/// // Compress returns the len.
108/// let compressed_len = bitpacker.compress(&my_data, &mut compressed[..], num_bits);
109///
110/// assert_eq!((num_bits as usize) *  BitPacker4x::BLOCK_LEN / 8, compressed_len);
111///
112/// // Decompressing
113/// let mut decompressed = vec![0u32; BitPacker4x::BLOCK_LEN];
114/// bitpacker.decompress(&compressed[..compressed_len], &mut decompressed[..], num_bits);
115///
116/// assert_eq!(&my_data, &decompressed);
117/// # }
118/// ```
119///
120///
121///
122/// # Examples with delta-encoding
123///
124/// Delta-encoding makes it possible to store sorted integers in an efficient manner.
125/// Rather than encoding the integers directly, the interval (or deltas) between each of them
126/// are computed and then encoded.
127///
128/// Decoding then requires to first decode the deltas and then operate a cumulative sum (also called
129/// integration or prefix sum) on them.
130///
131/// ```
132/// use bitpacking::{BitPacker4x, BitPacker};
133///
134/// # fn main() {
135/// # let my_data: Vec<u32> = vec![0, 5, 6, 8, 12, 21, 30, 38,
136/// # 46, 52, 59, 61, 62, 62, 71, 71, 73, 74, 76,
137/// # 77, 80, 87, 96, 99, 105, 114, 119, 121, 128,
138/// # 133, 138, 145, 152, 161, 161, 166, 175, 176,
139/// # 180, 186, 189, 193, 202, 211, 220, 224, 229,
140/// # 238, 247, 255, 261, 267, 267, 268, 269, 269,
141/// # 270, 271, 279, 283, 285, 291, 297, 303, 305,
142/// # 309, 310, 315, 316, 316, 321, 324, 329, 337,
143/// # 339, 342, 350, 355, 364, 373, 382, 386, 392,
144/// # 400, 408, 414, 423, 431, 433, 436, 441, 444,
145/// # 445, 454, 463, 463, 465, 472, 474, 477, 480,
146/// # 488, 493, 496, 501, 503, 509, 515, 519, 526,
147/// # 526, 532, 539, 542, 542, 542, 549, 557, 565,
148/// # 566, 573, 578, 580, 581, 585, 588, 588, 591];
149///
150///
151/// // The initial value is used to compute the first delta.
152/// // In most use cases, you will be compressing long increasing
153/// // integer sequences.
154/// //
155/// // You should probably pass an initial value of `0u32` to the
156/// // first block if you do not have any information.
157/// //
158/// // When encoding the second block however, you will want to pass the last
159/// // value of the first block.
160/// let initial_value = 0u32;
161///
162/// let bitpacker = BitPacker4x::new();
163///
164/// let num_bits: u8 = bitpacker.num_bits_sorted(initial_value, &my_data);
165///
166/// // A block will take at most 4 bytes per-integers.
167/// let mut compressed = vec![0u8; 4 * BitPacker4x::BLOCK_LEN];
168///
169/// # assert_eq!(num_bits, 4);
170///
171/// let compressed_len = bitpacker.compress_sorted(initial_value, &my_data, &mut compressed[..], num_bits);
172///
173/// assert_eq!((num_bits as usize) *  BitPacker4x::BLOCK_LEN / 8, compressed_len);
174///
175/// // Decompressing
176/// let mut decompressed = vec![0u32; BitPacker4x::BLOCK_LEN];
177///
178/// // The initial value must be the same as the one passed
179/// // when compressing the block.
180/// bitpacker.decompress_sorted(initial_value, &compressed[..compressed_len], &mut decompressed[..], num_bits);
181///
182/// assert_eq!(&my_data, &decompressed);
183/// # }
184pub trait BitPacker: Sized + Clone + Copy {
185    /// Number of `u32` per compressed block
186    const BLOCK_LEN: usize;
187
188    /// Checks the available instructions set on the current
189    /// CPU and returns the best available implementation.
190    ///
191    /// Calling `.new()` is extremely cheap, and does not
192    /// require any heap allocation. It is *not* required to cache
193    /// its result too aggressively.
194    fn new() -> Self;
195
196    /// Compress a block of `u32`.
197    ///
198    /// Assumes that the integers are all lower than `2^num_bits`.
199    /// The result is undefined if they are larger.
200    ///
201    /// Returns the amount of bytes of the compressed block.
202    ///
203    /// # Panics
204    ///
205    /// - Panics if the compressed destination array is too small
206    /// - Panics if `decompressed` length is not exactly the `BLOCK_LEN`.
207    fn compress(&self, decompressed: &[u32], compressed: &mut [u8], num_bits: u8) -> usize;
208
209    /// Delta encode and compressed the `decompressed` array.
210    ///
211    /// Assumes that the elements in the `decompressed` array are sorted.
212    /// `initial` will be used to compute the first `delta`.
213    ///
214    /// # Panics
215    ///
216    /// - Panics if `initial` is greater than `decompressed[0]`
217    /// - Panics if `decompressed` is not sorted
218    /// - Panics if `decompressed`'s length is not exactly `BLOCK_LEN`
219    /// - Panics if `compressed` is not large enough to receive the compressed data
220    /// - Panics if the compressed destination array is too small.
221    ///
222    /// Returns the amount of bytes of the compressed block.
223    ///
224    /// # Panics
225    ///
226    /// - Panics if the compressed array is too short.
227    /// - Panics if the decompressed array is not exactly the `BLOCK_LEN`.
228    fn compress_sorted(
229        &self,
230        initial: u32,
231        decompressed: &[u32],
232        compressed: &mut [u8],
233        num_bits: u8,
234    ) -> usize;
235
236    /// Delta encode and compress the `decompressed` array.
237    ///
238    /// Assumes that the elements in the `decompressed` array are strictly
239    /// monotonous, that is, each element is strictly greater than the previous.
240    ///
241    /// This codec can be more efficient that the simply sorted compressor by up to one bit per
242    /// integer. This has an important impact on saturated or nearly saturated datasets (almost
243    /// every number appears in sequence), but isn't very different from the sorted compressor
244    /// on more sparse datasets.
245    ///
246    /// # Panics
247    ///
248    /// - Panics if `initial` is greater or equal to `decompressed[0]`
249    /// - Panics if `decompressed` isn't strictly monotonic
250    /// - Panics if `decompressed`'s length is not exactly `BLOCK_LEN`
251    /// - Panics if `compressed` is not large enough to receive the compressed data
252    ///
253    /// Returns the amount of bytes in the compressed block.
254    fn compress_strictly_sorted(
255        &self,
256        initial: Option<u32>,
257        decompressed: &[u32],
258        compressed: &mut [u8],
259        num_bits: u8,
260    ) -> usize;
261
262    /// Decompress the `compress` array to the `decompressed` array.
263    ///
264    /// Returns the amount of bytes that were consumed.
265    ///
266    /// # Panics
267    ///
268    /// Panics if the compressed array is too short, or the decompressed array is too short.
269    fn decompress(&self, compressed: &[u8], decompressed: &mut [u32], num_bits: u8) -> usize;
270
271    /// Decompress the`compress`array to the `decompressed` array.
272    /// The `compressed` array is assumed to have been delta-encoded and compressed.
273    ///
274    /// `initial` must be the value that was passed as the `initial` argument compressing
275    /// the block.
276    ///
277    /// Returns the amount of bytes that have been read.
278    ///
279    /// # Panics
280    ///
281    /// - Panics if the compressed array is too short to contain `BLOCK_LEN` elements
282    /// - Panics if the decompressed array is too short.
283    fn decompress_sorted(
284        &self,
285        initial: u32,
286        compressed: &[u8],
287        decompressed: &mut [u32],
288        num_bits: u8,
289    ) -> usize;
290
291    /// Decompress the`compress`array to the `decompressed` array.
292    /// The `compressed` array is assumed to have been strict-delta-encoded and compressed.
293    ///
294    /// `initial` must be the value that was passed as the `initial` argument compressing
295    /// the block.
296    ///
297    /// Returns the amount of bytes that have been read.
298    ///
299    /// # Panics
300    ///
301    /// - Panics if the compressed array is too short to contain `BLOCK_LEN` elements
302    /// - Panics if the decompressed array is too short.
303
304    fn decompress_strictly_sorted(
305        &self,
306        initial: Option<u32>,
307        compressed: &[u8],
308        decompressed: &mut [u32],
309        num_bits: u8,
310    ) -> usize;
311
312    /// Returns the minimum number of bits used to represent the largest integer in the
313    /// `decompressed` block.
314    ///
315    /// # Panics
316    ///
317    /// Panics if `decompressed`'s len is not exactly `BLOCK_LEN`.
318    fn num_bits(&self, decompressed: &[u32]) -> u8;
319
320    /// Returns the minimum number of bits used to represent the largest `delta` in the deltas in the
321    /// `decompressed` block.
322    ///
323    /// # Panics
324    ///
325    /// Panics if `decompressed`'s len is not exactly `BLOCK_LEN`.
326    fn num_bits_sorted(&self, initial: u32, decompressed: &[u32]) -> u8;
327
328    /// Returns the minimum number of bits used to represent the largest `delta-1` in the deltas in the
329    /// `decompressed` block.
330    ///
331    /// # Panics
332    ///
333    /// Panics if `decompressed`'s len is not exactly `BLOCK_LEN`.
334    fn num_bits_strictly_sorted(&self, initial: Option<u32>, decompressed: &[u32]) -> u8;
335
336    /// Returns the size of a compressed block.
337    #[must_use]
338    fn compressed_block_size(num_bits: u8) -> usize {
339        Self::BLOCK_LEN * (num_bits as usize) / 8
340    }
341}
342
343/// Returns the most significant bit.&self,
344fn most_significant_bit(v: u32) -> u8 {
345    if v == 0 {
346        0
347    } else {
348        32u8 - (v.leading_zeros() as u8)
349    }
350}
351
352#[cfg(all(feature = "bitpacker1x", any(test, not(debug_assertions))))]
353mod bitpacker1x;
354#[cfg(all(feature = "bitpacker4x", any(test, not(debug_assertions))))]
355mod bitpacker4x;
356
357#[cfg(all(feature = "bitpacker1x", debug_assertions))]
358mod bitpacker1x_simple;
359#[cfg(all(feature = "bitpacker4x", debug_assertions))]
360mod bitpacker4x_simple;
361
362#[cfg(feature = "bitpacker8x")]
363mod bitpacker8x;
364
365#[cfg(all(feature = "bitpacker1x", not(debug_assertions)))]
366pub use bitpacker1x::BitPacker1x;
367#[cfg(all(feature = "bitpacker1x", debug_assertions))]
368pub use bitpacker1x_simple::BitPacker1x;
369
370#[cfg(all(feature = "bitpacker4x", not(debug_assertions)))]
371pub use bitpacker4x::BitPacker4x;
372#[cfg(all(feature = "bitpacker4x", debug_assertions))]
373pub use bitpacker4x_simple::BitPacker4x;
374
375#[cfg(feature = "bitpacker8x")]
376pub use bitpacker8x::BitPacker8x;
377
378#[cfg(test)]
379mod tests_unit {
380    use super::*;
381
382    #[test]
383    #[should_panic(expected = "`decompressed`'s len is not `BLOCK_LEN=128`")]
384    fn test_num_bits_block_too_long() {
385        let bit_packer = BitPacker4x::new();
386        let v = vec![0u32; BitPacker4x::BLOCK_LEN + 1];
387        bit_packer.num_bits(&v[..]);
388    }
389
390    #[test]
391    #[should_panic(expected = "`decompressed`'s len is not `BLOCK_LEN=128`")]
392    fn test_num_bits_block_too_short() {
393        let bit_packer = BitPacker4x::new();
394        let v = vec![0u32; BitPacker4x::BLOCK_LEN - 1];
395        bit_packer.num_bits(&v[..]);
396    }
397}
398
399#[cfg(test)]
400mod functional_tests {
401    use crate::{BitPacker, BitPacker4x};
402    use proptest::prelude::*;
403
404    proptest! {
405        #[test]
406        #[ignore]
407        fn check_block(
408            values in prop::collection::vec(u32::arbitrary(), BitPacker4x::BLOCK_LEN),
409        ) {
410            let bit_packer = BitPacker4x::new();
411            let bit_width = bit_packer.num_bits(&*values);
412
413            let mut block = vec![0u8; BitPacker4x::compressed_block_size(bit_width)];
414            bit_packer.compress(&values, &mut block, bit_width);
415
416            let mut decoded_values = vec![0x10101010; BitPacker4x::BLOCK_LEN];
417            bit_packer.decompress(&block, &mut decoded_values, bit_width);
418
419            prop_assert_eq!(values, decoded_values);
420        }
421
422        #[ignore]
423        #[test]
424        fn check_sorted_block(
425            (values, init_value) in prop::collection::vec(u32::arbitrary(), BitPacker4x::BLOCK_LEN).prop_flat_map(|mut values| {
426                values.sort_unstable();
427                let min_value = values[0];
428                (Just(values), 0..=min_value)
429            }),
430        ) {
431            let bit_packer = BitPacker4x::new();
432            let bit_width = bit_packer.num_bits_sorted(init_value, &*values);
433
434            let mut block = vec![0u8; BitPacker4x::compressed_block_size(bit_width)];
435            bit_packer.compress_sorted(init_value, &values, &mut block, bit_width);
436
437            let mut decoded_values = vec![0x10101010; BitPacker4x::BLOCK_LEN];
438            bit_packer.decompress_sorted(init_value, &block, &mut decoded_values, bit_width);
439
440            prop_assert_eq!(values, decoded_values);
441        }
442    }
443}