datasketches-cpp
Loading...
Searching...
No Matches
cpc_compressor_impl.hpp
1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20// author Kevin Lang, Oath Research
21
22#ifndef CPC_COMPRESSOR_IMPL_HPP_
23#define CPC_COMPRESSOR_IMPL_HPP_
24
25#include <cstdlib>
26#include <memory>
27#include <stdexcept>
28
29#include "common_defs.hpp"
30#include "compression_data.hpp"
31#include "cpc_util.hpp"
32#include "cpc_common.hpp"
33#include "count_zeros.hpp"
34
35namespace datasketches {
36
37// construct on first use
38template<typename A>
39cpc_compressor<A>& get_compressor() {
40 static cpc_compressor<A>* instance = new cpc_compressor<A>(); // use new for global initialization
41 static int reg_result = std::atexit(destroy_compressor<A>); // just to clean up a little more nicely; don't worry if it fails
42 unused(reg_result);
43 return *instance;
44}
45
46// register to call compressor destructor at exit
47template<typename A>
48void destroy_compressor() {
49 delete std::addressof(get_compressor<A>());
50}
51
52template<typename A>
53cpc_compressor<A>::cpc_compressor() {
54 make_decoding_tables();
55}
56
57template<typename A>
58cpc_compressor<A>::~cpc_compressor() {
59 free_decoding_tables();
60}
61
62template<typename A>
63uint8_t* cpc_compressor<A>::make_inverse_permutation(const uint8_t* permu, unsigned length) {
64 uint8_t* inverse = new uint8_t[length]; // use new for global initialization
65 for (unsigned i = 0; i < length; i++) {
66 inverse[permu[i]] = static_cast<uint8_t>(i);
67 }
68 for (unsigned i = 0; i < length; i++) {
69 if (permu[inverse[i]] != i) throw std::logic_error("inverse permutation error");
70 }
71 return inverse;
72}
73
74/* Given an encoding table that maps unsigned bytes to codewords
75 of length at most 12, this builds a size-4096 decoding table */
76// The second argument is typically 256, but can be other values such as 65.
77template<typename A>
78uint16_t* cpc_compressor<A>::make_decoding_table(const uint16_t* encoding_table, unsigned num_byte_values) {
79 uint16_t* decoding_table = new uint16_t[4096]; // use new for global initialization
80 for (unsigned byte_value = 0; byte_value < num_byte_values; byte_value++) {
81 const uint16_t encoding_entry = encoding_table[byte_value];
82 const uint16_t code_value = encoding_entry & 0xfff;
83 const uint8_t code_length = encoding_entry >> 12;
84 const uint16_t decoding_entry = static_cast<uint16_t>((code_length << 8) | byte_value);
85 const uint8_t garbage_length = 12 - code_length;
86 const uint32_t num_copies = 1 << garbage_length;
87 for (uint32_t garbage_bits = 0; garbage_bits < num_copies; garbage_bits++) {
88 const uint16_t extended_code_value = static_cast<uint16_t>(code_value | (garbage_bits << code_length));
89 decoding_table[extended_code_value & 0xfff] = decoding_entry;
90 }
91 }
92 return decoding_table;
93}
94
95template<typename A>
96void cpc_compressor<A>::validate_decoding_table(const uint16_t* decoding_table, const uint16_t* encoding_table) const {
97 for (int decode_this = 0; decode_this < 4096; decode_this++) {
98 const int tmp_d = decoding_table[decode_this];
99 const int decoded_byte = tmp_d & 0xff;
100 const int decoded_length = tmp_d >> 8;
101
102 const int tmp_e = encoding_table[decoded_byte];
103 const int encoded_bit_pattern = tmp_e & 0xfff;
104 const int encoded_length = tmp_e >> 12;
105
106 if (decoded_length != encoded_length) throw std::logic_error("decoded length error");
107 if (encoded_bit_pattern != (decode_this & ((1 << decoded_length) - 1))) throw std::logic_error("bit pattern error");
108 }
109}
110
111template<typename A>
112void cpc_compressor<A>::make_decoding_tables() {
113 length_limited_unary_decoding_table65 = make_decoding_table(length_limited_unary_encoding_table65, 65);
114 validate_decoding_table(
115 length_limited_unary_decoding_table65,
116 length_limited_unary_encoding_table65
117 );
118
119 for (int i = 0; i < (16 + 6); i++) {
120 decoding_tables_for_high_entropy_byte[i] = make_decoding_table(encoding_tables_for_high_entropy_byte[i], 256);
121 validate_decoding_table(
122 decoding_tables_for_high_entropy_byte[i],
123 encoding_tables_for_high_entropy_byte[i]
124 );
125 }
126
127 for (int i = 0; i < 16; i++) {
128 column_permutations_for_decoding[i] = make_inverse_permutation(column_permutations_for_encoding[i], 56);
129 }
130}
131
132template<typename A>
133void cpc_compressor<A>::free_decoding_tables() {
134 delete[] length_limited_unary_decoding_table65;
135 for (int i = 0; i < (16 + 6); i++) {
136 delete[] decoding_tables_for_high_entropy_byte[i];
137 }
138 for (int i = 0; i < 16; i++) {
139 delete[] column_permutations_for_decoding[i];
140 }
141}
142
143template<typename A>
144void cpc_compressor<A>::compress(const cpc_sketch_alloc<A>& source, compressed_state<A>& result) const {
145 switch (source.determine_flavor()) {
146 case cpc_sketch_alloc<A>::flavor::EMPTY:
147 break;
148 case cpc_sketch_alloc<A>::flavor::SPARSE:
149 compress_sparse_flavor(source, result);
150 if (result.window_data.size() > 0) throw std::logic_error("window is not expected");
151 if (result.table_data.size() == 0) throw std::logic_error("table is expected");
152 break;
153 case cpc_sketch_alloc<A>::flavor::HYBRID:
154 compress_hybrid_flavor(source, result);
155 if (result.window_data.size() > 0) throw std::logic_error("window is not expected");
156 if (result.table_data.size() == 0) throw std::logic_error("table is expected");
157 break;
158 case cpc_sketch_alloc<A>::flavor::PINNED:
159 compress_pinned_flavor(source, result);
160 if (result.window_data.size() == 0) throw std::logic_error("window is expected");
161 break;
162 case cpc_sketch_alloc<A>::flavor::SLIDING:
163 compress_sliding_flavor(source, result);
164 if (result.window_data.size() == 0) throw std::logic_error("window is expected");
165 break;
166 default: throw std::logic_error("Unknown sketch flavor");
167 }
168}
169
170template<typename A>
171void cpc_compressor<A>::uncompress(const compressed_state<A>& source, uncompressed_state<A>& target, uint8_t lg_k, uint32_t num_coupons) const {
172 switch (cpc_sketch_alloc<A>::determine_flavor(lg_k, num_coupons)) {
173 case cpc_sketch_alloc<A>::flavor::EMPTY:
174 target.table = u32_table<A>(2, 6 + lg_k, source.table_data.get_allocator());
175 break;
176 case cpc_sketch_alloc<A>::flavor::SPARSE:
177 uncompress_sparse_flavor(source, target, lg_k);
178 break;
179 case cpc_sketch_alloc<A>::flavor::HYBRID:
180 uncompress_hybrid_flavor(source, target, lg_k);
181 break;
182 case cpc_sketch_alloc<A>::flavor::PINNED:
183 if (source.window_data.size() == 0) throw std::logic_error("window is expected");
184 uncompress_pinned_flavor(source, target, lg_k, num_coupons);
185 break;
186 case cpc_sketch_alloc<A>::flavor::SLIDING:
187 uncompress_sliding_flavor(source, target, lg_k, num_coupons);
188 break;
189 default: std::logic_error("Unknown sketch flavor");
190 }
191}
192
193template<typename A>
194void cpc_compressor<A>::compress_sparse_flavor(const cpc_sketch_alloc<A>& source, compressed_state<A>& result) const {
195 if (source.sliding_window.size() > 0) throw std::logic_error("unexpected sliding window");
196 vector_u32 pairs = source.surprising_value_table.unwrapping_get_items();
197 u32_table<A>::introspective_insertion_sort(pairs.data(), 0, pairs.size());
198 compress_surprising_values(pairs, source.get_lg_k(), result);
199}
200
201template<typename A>
202void cpc_compressor<A>::uncompress_sparse_flavor(const compressed_state<A>& source, uncompressed_state<A>& target, uint8_t lg_k) const {
203 if (source.window_data.size() > 0) throw std::logic_error("unexpected sliding window");
204 if (source.table_data.size() == 0) throw std::logic_error("table is expected");
205 vector_u32 pairs = uncompress_surprising_values(source.table_data.data(), source.table_data_words, source.table_num_entries,
206 lg_k, source.table_data.get_allocator());
207 target.table = u32_table<A>::make_from_pairs(pairs.data(), source.table_num_entries, lg_k, pairs.get_allocator());
208}
209
210// This is complicated because it effectively builds a Sparse version
211// of a Pinned sketch before compressing it. Hence the name Hybrid.
212template<typename A>
213void cpc_compressor<A>::compress_hybrid_flavor(const cpc_sketch_alloc<A>& source, compressed_state<A>& result) const {
214 if (source.sliding_window.size() == 0) throw std::logic_error("no sliding window");
215 if (source.window_offset != 0) throw std::logic_error("window_offset != 0");
216 const uint32_t k = 1 << source.get_lg_k();
217 vector_u32 pairs_from_table = source.surprising_value_table.unwrapping_get_items();
218 const uint32_t num_pairs_from_table = static_cast<uint32_t>(pairs_from_table.size());
219 if (num_pairs_from_table > 0) u32_table<A>::introspective_insertion_sort(pairs_from_table.data(), 0, num_pairs_from_table);
220 const uint32_t num_pairs_from_window = source.get_num_coupons() - num_pairs_from_table; // because the window offset is zero
221
222 vector_u32 all_pairs = tricky_get_pairs_from_window(source.sliding_window.data(), k, num_pairs_from_window, num_pairs_from_table, source.get_allocator());
223
224 u32_table<A>::merge(
225 pairs_from_table.data(), 0, pairs_from_table.size(),
226 all_pairs.data(), num_pairs_from_table, num_pairs_from_window,
227 all_pairs.data(), 0
228 ); // note the overlapping subarray trick
229
230 compress_surprising_values(all_pairs, source.get_lg_k(), result);
231}
232
233template<typename A>
234void cpc_compressor<A>::uncompress_hybrid_flavor(const compressed_state<A>& source, uncompressed_state<A>& target, uint8_t lg_k) const {
235 if (source.window_data.size() > 0) throw std::logic_error("window is not expected");
236 if (source.table_data.size() == 0) throw std::logic_error("table is expected");
237 vector_u32 pairs = uncompress_surprising_values(source.table_data.data(), source.table_data_words, source.table_num_entries,
238 lg_k, source.table_data.get_allocator());
239
240 // In the hybrid flavor, some of these pairs actually
241 // belong in the window, so we will separate them out,
242 // moving the "true" pairs to the bottom of the array.
243 const uint32_t k = 1 << lg_k;
244 target.window.resize(k, 0); // important: zero the memory
245 uint32_t next_true_pair = 0;
246 for (uint32_t i = 0; i < source.table_num_entries; i++) {
247 const uint32_t row_col = pairs[i];
248 if (row_col == UINT32_MAX) throw std::logic_error("empty marker is not expected");
249 const uint8_t col = row_col & 63;
250 if (col < 8) {
251 const uint32_t row = row_col >> 6;
252 target.window[row] |= 1 << col; // set the window bit
253 } else {
254 pairs[next_true_pair++] = row_col; // move true pair down
255 }
256 }
257 target.table = u32_table<A>::make_from_pairs(pairs.data(), next_true_pair, lg_k, pairs.get_allocator());
258}
259
260template<typename A>
261void cpc_compressor<A>::compress_pinned_flavor(const cpc_sketch_alloc<A>& source, compressed_state<A>& result) const {
262 compress_sliding_window(source.sliding_window.data(), source.get_lg_k(), source.get_num_coupons(), result);
263 vector_u32 pairs = source.surprising_value_table.unwrapping_get_items();
264 if (pairs.size() > 0) {
265 // Here we subtract 8 from the column indices. Because they are stored in the low 6 bits
266 // of each row_col pair, and because no column index is less than 8 for a "Pinned" sketch,
267 // we can simply subtract 8 from the pairs themselves.
268
269 // shift the columns over by 8 positions before compressing (because of the window)
270 for (size_t i = 0; i < pairs.size(); i++) {
271 if ((pairs[i] & 63) < 8) throw std::logic_error("(pairs[i] & 63) < 8");
272 pairs[i] -= 8;
273 }
274
275 if (pairs.size() > 0) u32_table<A>::introspective_insertion_sort(pairs.data(), 0, pairs.size());
276 compress_surprising_values(pairs, source.get_lg_k(), result);
277 }
278}
279
280template<typename A>
281void cpc_compressor<A>::uncompress_pinned_flavor(const compressed_state<A>& source, uncompressed_state<A>& target,
282 uint8_t lg_k, uint32_t num_coupons) const {
283 if (source.window_data.size() == 0) throw std::logic_error("window is expected");
284 uncompress_sliding_window(source.window_data.data(), source.window_data_words, target.window, lg_k, num_coupons);
285 const uint32_t num_pairs = source.table_num_entries;
286 if (num_pairs == 0) {
287 target.table = u32_table<A>(2, 6 + lg_k, source.table_data.get_allocator());
288 } else {
289 if (source.table_data.size() == 0) throw std::logic_error("table is expected");
290 vector_u32 pairs = uncompress_surprising_values(source.table_data.data(), source.table_data_words, num_pairs,
291 lg_k, source.table_data.get_allocator());
292 // undo the compressor's 8-column shift
293 for (uint32_t i = 0; i < num_pairs; i++) {
294 if ((pairs[i] & 63) >= 56) throw std::logic_error("(pairs[i] & 63) >= 56");
295 pairs[i] += 8;
296 }
297 target.table = u32_table<A>::make_from_pairs(pairs.data(), num_pairs, lg_k, pairs.get_allocator());
298 }
299}
300
301template<typename A>
302void cpc_compressor<A>::compress_sliding_flavor(const cpc_sketch_alloc<A>& source, compressed_state<A>& result) const {
303 compress_sliding_window(source.sliding_window.data(), source.get_lg_k(), source.get_num_coupons(), result);
304 vector_u32 pairs = source.surprising_value_table.unwrapping_get_items();
305 if (pairs.size() > 0) {
306 // Here we apply a complicated transformation to the column indices, which
307 // changes the implied ordering of the pairs, so we must do it before sorting.
308
309 const uint8_t pseudo_phase = determine_pseudo_phase(source.get_lg_k(), source.get_num_coupons());
310 if (pseudo_phase >= 16) throw std::logic_error("unexpected pseudo phase for sliding flavor");
311 const uint8_t* permutation = column_permutations_for_encoding[pseudo_phase];
312
313 const uint8_t offset = source.window_offset;
314 if (offset > 56) throw std::out_of_range("offset out of range");
315
316 for (size_t i = 0; i < pairs.size(); i++) {
317 const uint32_t row_col = pairs[i];
318 const uint32_t row = row_col >> 6;
319 uint8_t col = row_col & 63;
320 // first rotate the columns into a canonical configuration: new = ((old - (offset+8)) + 64) mod 64
321 col = (col + 56 - offset) & 63;
322 if (col >= 56) throw std::out_of_range("col out of range");
323 // then apply the permutation
324 col = permutation[col];
325 pairs[i] = (row << 6) | col;
326 }
327
328 if (pairs.size() > 0) u32_table<A>::introspective_insertion_sort(pairs.data(), 0, pairs.size());
329 compress_surprising_values(pairs, source.get_lg_k(), result);
330 }
331}
332
333template<typename A>
334void cpc_compressor<A>::uncompress_sliding_flavor(const compressed_state<A>& source, uncompressed_state<A>& target,
335 uint8_t lg_k, uint32_t num_coupons) const {
336 if (source.window_data.size() == 0) throw std::logic_error("window is expected");
337 uncompress_sliding_window(source.window_data.data(), source.window_data_words, target.window, lg_k, num_coupons);
338 const uint32_t num_pairs = source.table_num_entries;
339 if (num_pairs == 0) {
340 target.table = u32_table<A>(2, 6 + lg_k, source.table_data.get_allocator());
341 } else {
342 if (source.table_data.size() == 0) throw std::logic_error("table is expected");
343 vector_u32 pairs = uncompress_surprising_values(source.table_data.data(), source.table_data_words, num_pairs,
344 lg_k, source.table_data.get_allocator());
345
346 const uint8_t pseudo_phase = determine_pseudo_phase(lg_k, num_coupons);
347 if (pseudo_phase >= 16) throw std::logic_error("unexpected pseudo phase for sliding flavor");
348 const uint8_t* permutation = column_permutations_for_decoding[pseudo_phase];
349
350 uint8_t offset = cpc_sketch_alloc<A>::determine_correct_offset(lg_k, num_coupons);
351 if (offset > 56) throw std::out_of_range("offset out of range");
352
353 for (uint32_t i = 0; i < num_pairs; i++) {
354 const uint32_t row_col = pairs[i];
355 const uint32_t row = row_col >> 6;
356 uint8_t col = row_col & 63;
357 if (col >= 56) throw std::out_of_range("col out of range");
358 // first undo the permutation
359 col = permutation[col];
360 // then undo the rotation: old = (new + (offset+8)) mod 64
361 col = (col + (offset + 8)) & 63;
362 pairs[i] = (row << 6) | col;
363 }
364
365 target.table = u32_table<A>::make_from_pairs(pairs.data(), num_pairs, lg_k, pairs.get_allocator());
366 }
367}
368
369template<typename A>
370void cpc_compressor<A>::compress_surprising_values(const vector_u32& pairs, uint8_t lg_k, compressed_state<A>& result) const {
371 const uint32_t k = 1 << lg_k;
372 const uint32_t num_pairs = static_cast<uint32_t>(pairs.size());
373 const uint8_t num_base_bits = golomb_choose_number_of_base_bits(k + num_pairs, num_pairs);
374 const uint64_t table_len = safe_length_for_compressed_pair_buf(k, num_pairs, num_base_bits);
375 result.table_data.resize(table_len);
376
377 uint32_t csv_length = low_level_compress_pairs(pairs.data(), static_cast<uint32_t>(pairs.size()), num_base_bits, result.table_data.data());
378
379 // At this point we could free the unused portion of the compression output buffer,
380 // but it is not necessary if it is temporary
381 // Note: realloc caused strange timing spikes for lgK = 11 and 12.
382
383 result.table_data_words = csv_length;
384 result.table_num_entries = num_pairs;
385}
386
387template<typename A>
388auto cpc_compressor<A>::uncompress_surprising_values(const uint32_t* data, uint32_t data_words, uint32_t num_pairs,
389 uint8_t lg_k, const A& allocator) const -> vector_u32 {
390 const uint32_t k = 1 << lg_k;
391 vector_u32 pairs(num_pairs, 0, allocator);
392 const uint8_t num_base_bits = golomb_choose_number_of_base_bits(k + num_pairs, num_pairs);
393 low_level_uncompress_pairs(pairs.data(), num_pairs, num_base_bits, data, data_words);
394 for (uint32_t i = 0; i < num_pairs; i++) {
395 if ((pairs[i] >> 6) >= k) throw std::out_of_range("row index out of range");
396 }
397 return pairs;
398}
399
400template<typename A>
401void cpc_compressor<A>::compress_sliding_window(const uint8_t* window, uint8_t lg_k, uint32_t num_coupons, compressed_state<A>& target) const {
402 const uint32_t k = 1 << lg_k;
403 const size_t window_buf_len = safe_length_for_compressed_window_buf(k);
404 target.window_data.resize(window_buf_len);
405 const uint8_t pseudo_phase = determine_pseudo_phase(lg_k, num_coupons);
406 size_t data_words = low_level_compress_bytes(window, k, encoding_tables_for_high_entropy_byte[pseudo_phase], target.window_data.data());
407
408 // At this point we could free the unused portion of the compression output buffer,
409 // but it is not necessary if it is temporary
410 // Note: realloc caused strange timing spikes for lgK = 11 and 12.
411
412 target.window_data_words = static_cast<uint32_t>(data_words);
413}
414
415template<typename A>
416void cpc_compressor<A>::uncompress_sliding_window(const uint32_t* data, uint32_t data_words, vector_bytes& window,
417 uint8_t lg_k, uint32_t num_coupons) const {
418 const uint32_t k = 1 << lg_k;
419 window.resize(k); // zeroing not needed here (unlike the Hybrid Flavor)
420 const uint8_t pseudo_phase = determine_pseudo_phase(lg_k, num_coupons);
421 low_level_uncompress_bytes(window.data(), k, decoding_tables_for_high_entropy_byte[pseudo_phase], data, data_words);
422}
423
424template<typename A>
425size_t cpc_compressor<A>::safe_length_for_compressed_pair_buf(uint32_t k, uint32_t num_pairs, uint8_t num_base_bits) {
426 // Long ybits = k + numPairs; // simpler and safer UB
427 // The following tighter UB on ybits is based on page 198
428 // of the textbook "Managing Gigabytes" by Witten, Moffat, and Bell.
429 // Notice that if numBaseBits == 0 it coincides with (k + numPairs).
430 const size_t ybits = num_pairs * (1 + num_base_bits) + (k >> num_base_bits);
431 const size_t xbits = 12 * num_pairs;
432 const size_t padding = num_base_bits > 10 ? 0 : 10 - num_base_bits;
433 return divide_longs_rounding_up(xbits + ybits + padding, 32);
434}
435
436// Explanation of padding: we write
437// 1) xdelta (huffman, provides at least 1 bit, requires 12-bit lookahead)
438// 2) ydeltaGolombHi (unary, provides at least 1 bit, requires 8-bit lookahead)
439// 3) ydeltaGolombLo (straight B bits).
440// So the 12-bit lookahead is the tight constraint, but there are at least (2 + B) bits emitted,
441// so we would be safe with max (0, 10 - B) bits of padding at the end of the bitstream.
442template<typename A>
443size_t cpc_compressor<A>::safe_length_for_compressed_window_buf(uint32_t k) { // measured in 32-bit words
444 const size_t bits = 12 * k + 11; // 11 bits of padding, due to 12-bit lookahead, with 1 bit certainly present.
445 return divide_longs_rounding_up(bits, 32);
446}
447
448template<typename A>
449uint8_t cpc_compressor<A>::determine_pseudo_phase(uint8_t lg_k, uint32_t c) {
450 const uint32_t k = 1 << lg_k;
451 // This mid-range logic produces pseudo-phases. They are used to select encoding tables.
452 // The thresholds were chosen by hand after looking at plots of measured compression.
453 if (1000 * c < 2375 * k) {
454 if ( 4 * c < 3 * k) return 16 + 0; // mid-range table
455 else if ( 10 * c < 11 * k) return 16 + 1; // mid-range table
456 else if ( 100 * c < 132 * k) return 16 + 2; // mid-range table
457 else if ( 3 * c < 5 * k) return 16 + 3; // mid-range table
458 else if (1000 * c < 1965 * k) return 16 + 4; // mid-range table
459 else if (1000 * c < 2275 * k) return 16 + 5; // mid-range table
460 else return 6; // steady-state table employed before its actual phase
461 } else { // This steady-state logic produces true phases. They are used to select
462 // encoding tables, and also column permutations for the "Sliding" flavor.
463 if (lg_k < 4) throw std::logic_error("lgK < 4");
464 const size_t tmp = c >> (lg_k - 4);
465 const uint8_t phase = tmp & 15;
466 if (phase >= 16) throw std::out_of_range("wrong phase");
467 return phase;
468 }
469}
470
471static inline void maybe_flush_bitbuf(uint64_t& bitbuf, uint8_t& bufbits, uint32_t* wordarr, uint32_t& wordindex) {
472 if (bufbits >= 32) {
473 wordarr[wordindex++] = bitbuf & 0xffffffff;
474 bitbuf = bitbuf >> 32;
475 bufbits -= 32;
476 }
477}
478
479static inline void maybe_fill_bitbuf(uint64_t& bitbuf, uint8_t& bufbits, const uint32_t* wordarr, uint32_t& wordindex,
480 uint32_t numwords, uint8_t minbits) {
481 if (bufbits < minbits) {
482 if (wordindex >= numwords) throw std::out_of_range("compressed data over-run");
483 bitbuf |= static_cast<uint64_t>(wordarr[wordindex++]) << bufbits;
484 bufbits += 32;
485 }
486}
487
488// This returns the number of compressed words that were actually used.
489// It is the caller's responsibility to ensure that the compressed_words array is long enough.
490template<typename A>
491uint32_t cpc_compressor<A>::low_level_compress_bytes(
492 const uint8_t* byte_array, // input
493 uint32_t num_bytes_to_encode,
494 const uint16_t* encoding_table,
495 uint32_t* compressed_words // output
496) const {
497 uint64_t bitbuf = 0; // bits are packed into this first, then are flushed to compressed_words
498 uint8_t bufbits = 0; // number of bits currently in bitbuf; must be between 0 and 31
499 uint32_t next_word_index = 0;
500
501 for (uint32_t byte_index = 0; byte_index < num_bytes_to_encode; byte_index++) {
502 const uint16_t code_info = encoding_table[byte_array[byte_index]];
503 const uint64_t code_val = code_info & 0xfff;
504 const uint8_t code_len = code_info >> 12;
505 bitbuf |= (code_val << bufbits);
506 bufbits += code_len;
507 maybe_flush_bitbuf(bitbuf, bufbits, compressed_words, next_word_index);
508 }
509
510 // Pad the bitstream with 11 zero-bits so that the decompressor's 12-bit peek can't overrun its input.
511 bufbits += 11;
512 maybe_flush_bitbuf(bitbuf, bufbits, compressed_words, next_word_index);
513
514 if (bufbits > 0) { // We are done encoding now, so we flush the bit buffer.
515 if (bufbits >= 32) throw std::logic_error("bufbits >= 32");
516 compressed_words[next_word_index++] = bitbuf & 0xffffffff;
517 bitbuf = 0; bufbits = 0; // not really necessary
518 }
519 return next_word_index;
520}
521
522template<typename A>
523void cpc_compressor<A>::low_level_uncompress_bytes(
524 uint8_t* byte_array, // output
525 uint32_t num_bytes_to_decode,
526 const uint16_t* decoding_table,
527 const uint32_t* compressed_words, // input
528 uint32_t num_compressed_words
529) const {
530 uint32_t word_index = 0;
531 uint64_t bitbuf = 0;
532 uint8_t bufbits = 0;
533
534 if (byte_array == nullptr) throw std::logic_error("byte_array == NULL");
535 if (decoding_table == nullptr) throw std::logic_error("decoding_table == NULL");
536 if (compressed_words == nullptr) throw std::logic_error("compressed_words == NULL");
537
538 for (uint32_t byte_index = 0; byte_index < num_bytes_to_decode; byte_index++) {
539 maybe_fill_bitbuf(bitbuf, bufbits, compressed_words, word_index, num_compressed_words, 12); // ensure 12 bits in bit buffer
540
541 const size_t peek12 = bitbuf & 0xfff; // These 12 bits will include an entire Huffman codeword.
542 const uint16_t lookup = decoding_table[peek12];
543 const uint8_t code_word_length = lookup >> 8;
544 const uint8_t decoded_byte = lookup & 0xff;
545 byte_array[byte_index] = decoded_byte;
546 bitbuf >>= code_word_length;
547 bufbits -= code_word_length;
548 }
549 // Buffer over-run should be impossible unless there is a bug.
550 // However, we might as well check here.
551 if (word_index > num_compressed_words) throw std::logic_error("word_index > num_compressed_words");
552}
553
554static inline uint64_t read_unary(
555 const uint32_t* compressed_words,
556 uint32_t num_compressed_words,
557 uint32_t& next_word_index,
558 uint64_t& bitbuf,
559 uint8_t& bufbits
560);
561
562static inline void write_unary(
563 uint32_t* compressed_words,
564 uint32_t& next_word_index_ptr,
565 uint64_t& bit_buf_ptr,
566 uint8_t& buf_bits_ptr,
567 uint64_t value
568);
569
570// Here "pairs" refers to row/column pairs that specify
571// the positions of surprising values in the bit matrix.
572
573// returns the number of compressed_words actually used
574template<typename A>
575uint32_t cpc_compressor<A>::low_level_compress_pairs(
576 const uint32_t* pair_array, // input
577 uint32_t num_pairs_to_encode,
578 uint8_t num_base_bits,
579 uint32_t* compressed_words // output
580) const {
581 uint64_t bitbuf = 0;
582 uint8_t bufbits = 0;
583 uint32_t next_word_index = 0;
584 const uint64_t golomb_lo_mask = (1 << num_base_bits) - 1;
585 uint32_t predicted_row_index = 0;
586 uint8_t predicted_col_index = 0;
587
588 for (uint32_t pair_index = 0; pair_index < num_pairs_to_encode; pair_index++) {
589 const uint32_t row_col = pair_array[pair_index];
590 const uint32_t row_index = row_col >> 6;
591 const uint8_t col_index = row_col & 63;
592
593 if (row_index != predicted_row_index) predicted_col_index = 0;
594
595 if (row_index < predicted_row_index) throw std::logic_error("row_index < predicted_row_index");
596 if (col_index < predicted_col_index) throw std::logic_error("col_index < predicted_col_index");
597
598 const uint32_t y_delta = row_index - predicted_row_index;
599 const uint8_t x_delta = col_index - predicted_col_index;
600
601 predicted_row_index = row_index;
602 predicted_col_index = col_index + 1;
603
604 const uint16_t code_info = length_limited_unary_encoding_table65[x_delta];
605 const uint64_t code_val = code_info & 0xfff;
606 const uint8_t code_len = static_cast<uint8_t>(code_info >> 12);
607 bitbuf |= code_val << bufbits;
608 bufbits += code_len;
609 maybe_flush_bitbuf(bitbuf, bufbits, compressed_words, next_word_index);
610
611 const uint64_t golomb_lo = y_delta & golomb_lo_mask;
612 const uint64_t golomb_hi = y_delta >> num_base_bits;
613
614 write_unary(compressed_words, next_word_index, bitbuf, bufbits, golomb_hi);
615
616 bitbuf |= golomb_lo << bufbits;
617 bufbits += num_base_bits;
618 maybe_flush_bitbuf(bitbuf, bufbits, compressed_words, next_word_index);
619 }
620
621 // Pad the bitstream so that the decompressor's 12-bit peek can't overrun its input.
622 const uint8_t padding = (num_base_bits > 10) ? 0 : 10 - num_base_bits;
623 bufbits += padding;
624 maybe_flush_bitbuf(bitbuf, bufbits, compressed_words, next_word_index);
625
626 if (bufbits > 0) { // We are done encoding now, so we flush the bit buffer
627 if (bufbits >= 32) throw std::logic_error("bufbits >= 32");
628 compressed_words[next_word_index++] = bitbuf & 0xffffffff;
629 bitbuf = 0; bufbits = 0; // not really necessary
630 }
631
632 return next_word_index;
633}
634
635template<typename A>
636void cpc_compressor<A>::low_level_uncompress_pairs(
637 uint32_t* pair_array, // output
638 uint32_t num_pairs_to_decode,
639 uint8_t num_base_bits,
640 const uint32_t* compressed_words, // input
641 uint32_t num_compressed_words
642) const {
643 uint32_t word_index = 0;
644 uint64_t bitbuf = 0;
645 uint8_t bufbits = 0;
646 const uint64_t golomb_lo_mask = (1 << num_base_bits) - 1;
647 uint32_t predicted_row_index = 0;
648 uint8_t predicted_col_index = 0;
649
650 // for each pair we need to read:
651 // x_delta (12-bit length-limited unary)
652 // y_delta_hi (unary)
653 // y_delta_lo (basebits)
654
655 for (uint32_t pair_index = 0; pair_index < num_pairs_to_decode; pair_index++) {
656 maybe_fill_bitbuf(bitbuf, bufbits, compressed_words, word_index, num_compressed_words, 12); // ensure 12 bits in bit buffer
657 const size_t peek12 = bitbuf & 0xfff;
658 const uint16_t lookup = length_limited_unary_decoding_table65[peek12];
659 const uint8_t code_word_length = lookup >> 8;
660 const int8_t x_delta = lookup & 0xff;
661 bitbuf >>= code_word_length;
662 bufbits -= code_word_length;
663
664 const uint64_t golomb_hi = read_unary(compressed_words, num_compressed_words, word_index, bitbuf, bufbits);
665
666 maybe_fill_bitbuf(bitbuf, bufbits, compressed_words, word_index, num_compressed_words, num_base_bits); // ensure num_base_bits in bit buffer
667 const uint64_t golomb_lo = bitbuf & golomb_lo_mask;
668 bitbuf >>= num_base_bits;
669 bufbits -= num_base_bits;
670 const int64_t y_delta = (golomb_hi << num_base_bits) | golomb_lo;
671
672 // Now that we have x_delta and y_delta, we can compute the pair's row and column
673 if (y_delta > 0) predicted_col_index = 0;
674 const uint32_t row_index = static_cast<uint32_t>(predicted_row_index + y_delta);
675 const uint8_t col_index = predicted_col_index + x_delta;
676 if (col_index > 63) throw std::out_of_range("column index out of range");
677 const uint32_t row_col = (row_index << 6) | col_index;
678 pair_array[pair_index] = row_col;
679 predicted_row_index = row_index;
680 predicted_col_index = col_index + 1;
681 }
682 if (word_index > num_compressed_words) throw std::logic_error("word_index > num_compressed_words"); // check for buffer over-run
683}
684
685uint64_t read_unary(
686 const uint32_t* compressed_words,
687 uint32_t num_compressed_words,
688 uint32_t& next_word_index,
689 uint64_t& bitbuf,
690 uint8_t& bufbits
691) {
692 if (compressed_words == nullptr) throw std::logic_error("compressed_words == NULL");
693 size_t subtotal = 0;
694 while (true) {
695 maybe_fill_bitbuf(bitbuf, bufbits, compressed_words, next_word_index, num_compressed_words, 8); // ensure 8 bits in bit buffer
696
697 const uint8_t peek8 = bitbuf & 0xff; // These 8 bits include either all or part of the Unary codeword
698 const uint8_t trailing_zeros = byte_trailing_zeros_table[peek8];
699
700 if (trailing_zeros > 8) throw std::out_of_range("trailing_zeros out of range");
701 if (trailing_zeros < 8) {
702 bufbits -= 1 + trailing_zeros;
703 bitbuf >>= 1 + trailing_zeros;
704 return subtotal + trailing_zeros;
705 }
706 // The codeword was partial, so read some more
707 subtotal += 8;
708 bufbits -= 8;
709 bitbuf >>= 8;
710 }
711}
712
713void write_unary(
714 uint32_t* compressed_words,
715 uint32_t& next_word_index,
716 uint64_t& bitbuf,
717 uint8_t& bufbits,
718 uint64_t value
719) {
720 if (compressed_words == nullptr) throw std::logic_error("compressed_words == NULL");
721 if (bufbits > 31) throw std::out_of_range("bufbits out of range");
722
723 uint64_t remaining = value;
724
725 while (remaining >= 16) {
726 remaining -= 16;
727 // Here we output 16 zeros, but we don't need to physically write them into bitbuf
728 // because it already contains zeros in that region.
729 bufbits += 16; // Record the fact that 16 bits of output have occurred.
730 maybe_flush_bitbuf(bitbuf, bufbits, compressed_words, next_word_index);
731 }
732
733 if (remaining > 15) throw std::out_of_range("remaining out of range");
734
735 const uint64_t the_unary_code = 1ULL << remaining;
736 bitbuf |= the_unary_code << bufbits;
737 bufbits += static_cast<uint8_t>(remaining + 1);
738 maybe_flush_bitbuf(bitbuf, bufbits, compressed_words, next_word_index);
739}
740
741// The empty space that this leaves at the beginning of the output array
742// will be filled in later by the caller.
743template<typename A>
744auto cpc_compressor<A>::tricky_get_pairs_from_window(const uint8_t* window, uint32_t k, uint32_t num_pairs_to_get,
745 uint32_t empty_space, const A& allocator) -> vector_u32 {
746 const size_t output_length = empty_space + num_pairs_to_get;
747 vector_u32 pairs(output_length, 0, allocator);
748 size_t pair_index = empty_space;
749 for (unsigned row_index = 0; row_index < k; row_index++) {
750 uint8_t byte = window[row_index];
751 while (byte != 0) {
752 const uint8_t col_index = byte_trailing_zeros_table[byte];
753 byte = byte ^ (1 << col_index); // erase the 1
754 pairs[pair_index++] = (row_index << 6) | col_index;
755 }
756 }
757 if (pair_index != output_length) throw std::logic_error("pair_index != output_length");
758 return pairs;
759}
760
761// returns an integer that is between
762// zero and ceiling(log_2(k)) - 1, inclusive
763template<typename A>
764uint8_t cpc_compressor<A>::golomb_choose_number_of_base_bits(uint32_t k, uint64_t count) {
765 if (k < 1) throw std::invalid_argument("golomb_choose_number_of_base_bits: k < 1");
766 if (count < 1) throw std::invalid_argument("golomb_choose_number_of_base_bits: count < 1");
767 const uint64_t quotient = (k - count) / count; // integer division
768 if (quotient == 0) return 0;
769 else return floor_log2_of_long(quotient);
770}
771
772} /* namespace datasketches */
773
774#endif
High performance C++ implementation of Compressed Probabilistic Counting (CPC) Sketch.
Definition cpc_sketch.hpp:64
DataSketches namespace.
Definition binomial_bounds.hpp:38