Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
pola-rs
GitHub Repository: pola-rs/polars
Path: blob/main/crates/polars-parquet/src/parquet/encoding/bitpacked/pack.rs
7887 views
1
#![allow(unsafe_op_in_unsafe_fn)]
2
/// Macro that generates a packing function taking the number of bits as a const generic
3
macro_rules! pack_impl {
4
($t:ty, $bytes:literal, $bits:tt, $bits_minus_one:tt) => {
5
// Adapted from https://github.com/quickwit-oss/bitpacking
6
pub unsafe fn pack<const NUM_BITS: usize>(input: &[$t; $bits], output: &mut [u8]) {
7
if NUM_BITS == 0 {
8
for out in output {
9
*out = 0;
10
}
11
return;
12
}
13
assert!(NUM_BITS <= $bits);
14
assert!(output.len() >= NUM_BITS * $bytes);
15
16
let input_ptr = input.as_ptr();
17
let mut output_ptr = output.as_mut_ptr() as *mut $t;
18
let mut out_register: $t = read_unaligned(input_ptr);
19
20
if $bits == NUM_BITS {
21
write_unaligned(output_ptr, out_register);
22
output_ptr = output_ptr.offset(1);
23
}
24
25
// Using microbenchmark (79d1fff), unrolling this loop is over 10x
26
// faster than not (>20x faster than old algorithm)
27
seq_macro!(i in 1..$bits_minus_one {
28
let bits_filled: usize = i * NUM_BITS;
29
let inner_cursor: usize = bits_filled % $bits;
30
let remaining: usize = $bits - inner_cursor;
31
32
let offset_ptr = input_ptr.add(i);
33
let in_register: $t = read_unaligned(offset_ptr);
34
35
out_register =
36
if inner_cursor > 0 {
37
out_register | (in_register << inner_cursor)
38
} else {
39
in_register
40
};
41
42
if remaining <= NUM_BITS {
43
write_unaligned(output_ptr, out_register);
44
output_ptr = output_ptr.offset(1);
45
if 0 < remaining && remaining < NUM_BITS {
46
out_register = in_register >> remaining
47
}
48
}
49
});
50
51
let in_register: $t = read_unaligned(input_ptr.add($bits - 1));
52
out_register = if $bits - NUM_BITS > 0 {
53
out_register | (in_register << ($bits - NUM_BITS))
54
} else {
55
in_register
56
};
57
write_unaligned(output_ptr, out_register)
58
}
59
};
60
}
61
62
/// Macro that generates pack functions that accept num_bits as a parameter
63
macro_rules! pack {
64
($name:ident, $t:ty, $bytes:literal, $bits:tt, $bits_minus_one:tt) => {
65
mod $name {
66
use std::ptr::{read_unaligned, write_unaligned};
67
pack_impl!($t, $bytes, $bits, $bits_minus_one);
68
}
69
70
/// Pack unpacked `input` into `output` with a bit width of `num_bits`
71
pub fn $name(input: &[$t; $bits], output: &mut [u8], num_bits: usize) {
72
// This will get optimised into a jump table
73
seq_macro!(i in 0..=$bits {
74
if i == num_bits {
75
unsafe {
76
return $name::pack::<i>(input, output);
77
}
78
}
79
});
80
unreachable!("invalid num_bits {}", num_bits);
81
}
82
};
83
}
84
85
pack!(pack16, u16, 2, 16, 15);
86
pack!(pack32, u32, 4, 32, 31);
87
pack!(pack64, u64, 8, 64, 63);
88
89
#[cfg(test)]
90
mod tests {
91
use rand::distr::{Distribution, Uniform};
92
93
use super::super::unpack::*;
94
use super::*;
95
96
#[test]
97
fn test_u32() {
98
let input = [
99
0u32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0u32, 1, 2, 3, 4, 5, 6, 7, 8,
100
9, 10, 11, 12, 13, 14, 15,
101
];
102
for num_bits in 4..32 {
103
let mut output = [0u8; 32 * 4];
104
pack32(&input, &mut output, num_bits);
105
let mut other = [0u32; 32];
106
unpack32(&output, &mut other, num_bits);
107
assert_eq!(other, input);
108
}
109
}
110
111
#[test]
112
fn test_u32_random() {
113
let mut rng = rand::rng();
114
let mut random_array = [0u32; 32];
115
let between = Uniform::new(0, 131_072).unwrap();
116
for num_bits in 17..=32 {
117
for i in &mut random_array {
118
*i = between.sample(&mut rng);
119
}
120
let mut output = [0u8; 32 * 4];
121
pack32(&random_array, &mut output, num_bits);
122
let mut other = [0u32; 32];
123
unpack32(&output, &mut other, num_bits);
124
assert_eq!(other, random_array);
125
}
126
}
127
128
#[test]
129
fn test_u64_random() {
130
let mut rng = rand::rng();
131
let mut random_array = [0u64; 64];
132
let between = Uniform::new(0, 131_072).unwrap();
133
for num_bits in 17..=64 {
134
for i in &mut random_array {
135
*i = between.sample(&mut rng);
136
}
137
let mut output = [0u8; 64 * 8];
138
pack64(&random_array, &mut output, num_bits);
139
let mut other = [0u64; 64];
140
unpack64(&output, &mut other, num_bits);
141
assert_eq!(other, random_array);
142
}
143
}
144
}
145
146