/src/c-blosc2/blosc/blosclz.c
Line | Count | Source |
1 | | /********************************************************************* |
2 | | Blosc - Blocked Shuffling and Compression Library |
3 | | |
4 | | Copyright (c) 2021 Blosc Development Team <blosc@blosc.org> |
5 | | https://blosc.org |
6 | | License: BSD 3-Clause (see LICENSE.txt) |
7 | | |
8 | | See LICENSE.txt for details about copyright and rights to use. |
9 | | **********************************************************************/ |
10 | | |
11 | | /********************************************************************* |
12 | | The code in this file is heavily based on FastLZ, a lightning-fast |
13 | | lossless compression library. See LICENSES/FASTLZ.txt for details. |
14 | | **********************************************************************/ |
15 | | |
16 | | |
17 | | #include "blosclz.h" |
18 | | #include "fastcopy.h" |
19 | | #include "blosc2/blosc2-common.h" |
20 | | |
21 | | #include <stdbool.h> |
22 | | #include <stdio.h> |
23 | | #include <stdint.h> |
24 | | #include <string.h> |
25 | | |
26 | | /* |
27 | | * Give hints to the compiler for branch prediction optimization. |
28 | | * This is not necessary anymore with modern CPUs. |
29 | | */ |
30 | | #if 0 && defined(__GNUC__) && (__GNUC__ > 2) |
31 | | #define BLOSCLZ_LIKELY(c) (__builtin_expect((c), 1)) |
32 | | #define BLOSCLZ_UNLIKELY(c) (__builtin_expect((c), 0)) |
33 | | #else |
34 | 45.4M | #define BLOSCLZ_LIKELY(c) (c) |
35 | 104M | #define BLOSCLZ_UNLIKELY(c) (c) |
36 | | #endif |
37 | | |
38 | | /* |
39 | | * Use inlined functions for supported systems. |
40 | | */ |
41 | | #if defined(_MSC_VER) && !defined(__cplusplus) /* Visual Studio */ |
42 | | #define inline __inline /* Visual C is not C99, but supports some kind of inline */ |
43 | | #endif |
44 | | |
45 | 1.85M | #define MAX_COPY 32U |
46 | 48.1M | #define MAX_DISTANCE 8191 |
47 | 45.2M | #define MAX_FARDISTANCE (65535 + MAX_DISTANCE - 1) |
48 | | |
49 | | #ifdef BLOSC_STRICT_ALIGN |
50 | | #define BLOSCLZ_READU16(p) ((p)[0] | (p)[1]<<8) |
51 | | #define BLOSCLZ_READU32(p) ((p)[0] | (p)[1]<<8 | (p)[2]<<16 | (p)[3]<<24) |
52 | | #else |
53 | | #define BLOSCLZ_READU16(p) *((const uint16_t*)(p)) |
54 | 97.2M | #define BLOSCLZ_READU32(p) *((const uint32_t*)(p)) |
55 | | #endif |
56 | | |
57 | 453k | #define HASH_LOG (14U) |
58 | | |
59 | | // This is used in LZ4 and seems to work pretty well here too |
60 | 48.0M | #define HASH_FUNCTION(v, s, h) { \ |
61 | 48.0M | (v) = ((s) * 2654435761U) >> (32U - (h)); \ |
62 | 48.0M | } |
63 | | |
64 | | |
65 | | #if defined(__AVX2__) |
66 | | static uint8_t *get_run_32(uint8_t *ip, const uint8_t *ip_bound, const uint8_t *ref) { |
67 | | uint8_t x = ip[-1]; |
68 | | |
69 | | while (ip < (ip_bound - (sizeof(__m256i)))) { |
70 | | __m256i value, value2, cmp; |
71 | | /* Broadcast the value for every byte in a 256-bit register */ |
72 | | memset(&value, x, sizeof(__m256i)); |
73 | | value2 = _mm256_loadu_si256((__m256i *)ref); |
74 | | cmp = _mm256_cmpeq_epi64(value, value2); |
75 | | if ((unsigned)_mm256_movemask_epi8(cmp) != 0xFFFFFFFF) { |
76 | | /* Return the byte that starts to differ */ |
77 | | while (*ref++ == x) ip++; |
78 | | return ip; |
79 | | } |
80 | | else { |
81 | | ip += sizeof(__m256i); |
82 | | ref += sizeof(__m256i); |
83 | | } |
84 | | } |
85 | | /* Look into the remainder */ |
86 | | while ((ip < ip_bound) && (*ref++ == x)) ip++; |
87 | | return ip; |
88 | | } |
89 | | #endif |
90 | | |
91 | | #if defined(__SSE2__) |
92 | 0 | uint8_t *get_run_16(uint8_t *ip, const uint8_t *ip_bound, const uint8_t *ref) { |
93 | 0 | uint8_t x = ip[-1]; |
94 | |
|
95 | 0 | while (ip < (ip_bound - sizeof(__m128i))) { |
96 | 0 | __m128i value, value2, cmp; |
97 | | /* Broadcast the value for every byte in a 128-bit register */ |
98 | 0 | memset(&value, x, sizeof(__m128i)); |
99 | 0 | value2 = _mm_loadu_si128((__m128i *)ref); |
100 | 0 | cmp = _mm_cmpeq_epi32(value, value2); |
101 | 0 | if (_mm_movemask_epi8(cmp) != 0xFFFF) { |
102 | | /* Return the byte that starts to differ */ |
103 | 0 | while (*ref++ == x) ip++; |
104 | 0 | return ip; |
105 | 0 | } |
106 | 0 | else { |
107 | 0 | ip += sizeof(__m128i); |
108 | 0 | ref += sizeof(__m128i); |
109 | 0 | } |
110 | 0 | } |
111 | | /* Look into the remainder */ |
112 | 0 | while ((ip < ip_bound) && (*ref++ == x)) ip++; |
113 | 0 | return ip; |
114 | 0 | } |
115 | | |
116 | | #endif |
117 | | |
118 | | |
119 | 1.16M | static uint8_t *get_run(uint8_t *ip, const uint8_t *ip_bound, const uint8_t *ref) { |
120 | 1.16M | uint8_t x = ip[-1]; |
121 | 1.16M | int64_t value, value2; |
122 | | /* Broadcast the value for every byte in a 64-bit register */ |
123 | 1.16M | memset(&value, x, 8); |
124 | | /* safe because the outer check against ip limit */ |
125 | 2.04M | while (ip < (ip_bound - sizeof(int64_t))) { |
126 | | #if defined(BLOSC_STRICT_ALIGN) |
127 | | memcpy(&value2, ref, 8); |
128 | | #else |
129 | 2.03M | value2 = ((int64_t*)ref)[0]; |
130 | 2.03M | #endif |
131 | 2.03M | if (value != value2) { |
132 | | /* Return the byte that starts to differ */ |
133 | 3.58M | while (*ref++ == x) ip++; |
134 | 1.14M | return ip; |
135 | 1.14M | } |
136 | 885k | else { |
137 | 885k | ip += 8; |
138 | 885k | ref += 8; |
139 | 885k | } |
140 | 2.03M | } |
141 | | /* Look into the remainder */ |
142 | 62.0k | while ((ip < ip_bound) && (*ref++ == x)) ip++; |
143 | 15.9k | return ip; |
144 | 1.16M | } |
145 | | |
146 | | |
147 | | /* Return the byte that starts to differ */ |
148 | 0 | uint8_t *get_match(uint8_t *ip, const uint8_t *ip_bound, const uint8_t *ref) { |
149 | 0 | #if !defined(BLOSC_STRICT_ALIGN) |
150 | 0 | while (ip < (ip_bound - sizeof(int64_t))) { |
151 | 0 | if (*(int64_t*)ref != *(int64_t*)ip) { |
152 | | /* Return the byte that starts to differ */ |
153 | 0 | while (*ref++ == *ip++) {} |
154 | 0 | return ip; |
155 | 0 | } |
156 | 0 | else { |
157 | 0 | ip += sizeof(int64_t); |
158 | 0 | ref += sizeof(int64_t); |
159 | 0 | } |
160 | 0 | } |
161 | 0 | #endif |
162 | | /* Look into the remainder */ |
163 | 0 | while ((ip < ip_bound) && (*ref++ == *ip++)) {} |
164 | 0 | return ip; |
165 | 0 | } |
166 | | |
167 | | |
168 | | #if defined(__SSE2__) |
169 | 8.37M | static uint8_t *get_match_16(uint8_t *ip, const uint8_t *ip_bound, const uint8_t *ref) { |
170 | 8.37M | __m128i value, value2, cmp; |
171 | | |
172 | 11.5M | while (ip < (ip_bound - sizeof(__m128i))) { |
173 | 11.4M | value = _mm_loadu_si128((__m128i *) ip); |
174 | 11.4M | value2 = _mm_loadu_si128((__m128i *) ref); |
175 | 11.4M | cmp = _mm_cmpeq_epi32(value, value2); |
176 | 11.4M | if (_mm_movemask_epi8(cmp) != 0xFFFF) { |
177 | | /* Return the byte that starts to differ */ |
178 | 24.3M | while (*ref++ == *ip++) {} |
179 | 8.29M | return ip; |
180 | 8.29M | } |
181 | 3.15M | else { |
182 | 3.15M | ip += sizeof(__m128i); |
183 | 3.15M | ref += sizeof(__m128i); |
184 | 3.15M | } |
185 | 11.4M | } |
186 | | /* Look into the remainder */ |
187 | 448k | while ((ip < ip_bound) && (*ref++ == *ip++)) {} |
188 | 82.5k | return ip; |
189 | 8.37M | } |
190 | | #endif |
191 | | |
192 | | |
193 | | #if defined(__AVX2__) |
194 | | static uint8_t *get_match_32(uint8_t *ip, const uint8_t *ip_bound, const uint8_t *ref) { |
195 | | |
196 | | while (ip < (ip_bound - sizeof(__m256i))) { |
197 | | __m256i value, value2, cmp; |
198 | | value = _mm256_loadu_si256((__m256i *) ip); |
199 | | value2 = _mm256_loadu_si256((__m256i *)ref); |
200 | | cmp = _mm256_cmpeq_epi64(value, value2); |
201 | | if ((unsigned)_mm256_movemask_epi8(cmp) != 0xFFFFFFFF) { |
202 | | /* Return the byte that starts to differ */ |
203 | | while (*ref++ == *ip++) {} |
204 | | return ip; |
205 | | } |
206 | | else { |
207 | | ip += sizeof(__m256i); |
208 | | ref += sizeof(__m256i); |
209 | | } |
210 | | } |
211 | | /* Look into the remainder */ |
212 | | while ((ip < ip_bound) && (*ref++ == *ip++)) {} |
213 | | return ip; |
214 | | } |
215 | | #endif |
216 | | |
217 | | |
218 | 9.54M | static uint8_t* get_run_or_match(uint8_t* ip, uint8_t* ip_bound, const uint8_t* ref, bool run) { |
219 | 9.54M | if (BLOSCLZ_UNLIKELY(run)) { |
220 | | #if defined(__AVX2__) |
221 | | // Extensive experiments on AMD Ryzen3 say that regular get_run is faster |
222 | | // ip = get_run_32(ip, ip_bound, ref); |
223 | | ip = get_run(ip, ip_bound, ref); |
224 | | #elif defined(__SSE2__) |
225 | | // Extensive experiments on AMD Ryzen3 say that regular get_run is faster |
226 | | // ip = get_run_16(ip, ip_bound, ref); |
227 | 1.16M | ip = get_run(ip, ip_bound, ref); |
228 | | #else |
229 | | ip = get_run(ip, ip_bound, ref); |
230 | | #endif |
231 | 1.16M | } |
232 | 8.37M | else { |
233 | | #if defined(__AVX2__) |
234 | | // Extensive experiments on AMD Ryzen3 say that regular get_match_16 is faster |
235 | | // ip = get_match_32(ip, ip_bound, ref); |
236 | | ip = get_match_16(ip, ip_bound, ref); |
237 | | #elif defined(__SSE2__) |
238 | | ip = get_match_16(ip, ip_bound, ref); |
239 | | #else |
240 | | ip = get_match(ip, ip_bound, ref); |
241 | | #endif |
242 | 8.37M | } |
243 | | |
244 | 9.54M | return ip; |
245 | 9.54M | } |
246 | | |
247 | | |
248 | 19.0M | #define LITERAL(ip, op, op_limit, anchor, copy) { \ |
249 | 19.0M | if (BLOSCLZ_UNLIKELY((op_limit) - (op) < 2)) \ |
250 | 19.0M | goto out; \ |
251 | 19.0M | *(op)++ = *(anchor)++; \ |
252 | 19.0M | (ip) = (anchor); \ |
253 | 19.0M | (copy)++; \ |
254 | 19.0M | if (BLOSCLZ_UNLIKELY((copy) == MAX_COPY)) { \ |
255 | 349k | (copy) = 0; \ |
256 | 349k | *(op)++ = MAX_COPY-1; \ |
257 | 349k | } \ |
258 | 19.0M | } |
259 | | |
260 | 23.9M | #define LITERAL2(ip, anchor, copy) { \ |
261 | 23.9M | oc++; (anchor)++; \ |
262 | 23.9M | (ip) = (anchor); \ |
263 | 23.9M | (copy)++; \ |
264 | 23.9M | if (BLOSCLZ_UNLIKELY((copy) == MAX_COPY)) { \ |
265 | 568k | (copy) = 0; \ |
266 | 568k | oc++; \ |
267 | 568k | } \ |
268 | 23.9M | } |
269 | | |
270 | 561k | #define MATCH_SHORT(op, op_limit, len, distance) { \ |
271 | 561k | if (BLOSCLZ_UNLIKELY((op_limit) - (op) < 2)) \ |
272 | 561k | goto out; \ |
273 | 561k | *(op)++ = (uint8_t)(((len) << 5U) + ((distance) >> 8U));\ |
274 | 561k | *(op)++ = (uint8_t)(((distance) & 255U)); \ |
275 | 561k | } |
276 | | |
277 | 866k | #define MATCH_LONG(op, op_limit, len, distance) { \ |
278 | 866k | if (BLOSCLZ_UNLIKELY((op_limit) - (op) < 1)) \ |
279 | 866k | goto out; \ |
280 | 866k | *(op)++ = (uint8_t)((7U << 5U) + ((distance) >> 8U)); \ |
281 | 938k | for ((len) -= 7; (len) >= 255; (len) -= 255) { \ |
282 | 71.1k | if (BLOSCLZ_UNLIKELY((op_limit) - (op) < 1)) \ |
283 | 71.1k | goto out; \ |
284 | 71.1k | *(op)++ = 255; \ |
285 | 71.1k | } \ |
286 | 866k | if (BLOSCLZ_UNLIKELY((op_limit) - (op) < 2)) \ |
287 | 866k | goto out; \ |
288 | 866k | *(op)++ = (uint8_t)(len); \ |
289 | 866k | *(op)++ = (uint8_t)(((distance) & 255U)); \ |
290 | 866k | } |
291 | | |
292 | 3.99k | #define MATCH_SHORT_FAR(op, op_limit, len, distance) { \ |
293 | 3.99k | if (BLOSCLZ_UNLIKELY((op_limit) - (op) < 4)) \ |
294 | 3.99k | goto out; \ |
295 | 3.99k | *(op)++ = (uint8_t)(((len) << 5U) + 31); \ |
296 | 3.97k | *(op)++ = 255; \ |
297 | 3.97k | *(op)++ = (uint8_t)((distance) >> 8U); \ |
298 | 3.97k | *(op)++ = (uint8_t)((distance) & 255U); \ |
299 | 3.97k | } |
300 | | |
301 | 36.4k | #define MATCH_LONG_FAR(op, op_limit, len, distance) { \ |
302 | 36.4k | if (BLOSCLZ_UNLIKELY((op_limit) - (op) < 1)) \ |
303 | 36.4k | goto out; \ |
304 | 36.4k | *(op)++ = (7U << 5U) + 31; \ |
305 | 53.1k | for ((len) -= 7; (len) >= 255; (len) -= 255) { \ |
306 | 16.6k | if (BLOSCLZ_UNLIKELY((op_limit) - (op) < 1)) \ |
307 | 16.6k | goto out; \ |
308 | 16.6k | *(op)++ = 255; \ |
309 | 16.6k | } \ |
310 | 36.4k | if (BLOSCLZ_UNLIKELY((op_limit) - (op) < 4)) \ |
311 | 36.4k | goto out; \ |
312 | 36.4k | *(op)++ = (uint8_t)(len); \ |
313 | 36.4k | *(op)++ = 255; \ |
314 | 36.4k | *(op)++ = (uint8_t)((distance) >> 8U); \ |
315 | 36.4k | *(op)++ = (uint8_t)((distance) & 255U); \ |
316 | 36.4k | } |
317 | | |
318 | | |
319 | | // Get a guess for the compressed size of a buffer |
320 | 50.4k | static double get_cratio(uint8_t* ibase, int maxlen, int minlen, int ipshift, uint32_t htab[], int8_t hashlog) { |
321 | 50.4k | uint8_t* ip = ibase; |
322 | 50.4k | int32_t oc = 0; |
323 | 50.4k | const uint16_t hashlen = (1U << (uint8_t)hashlog); |
324 | 50.4k | uint32_t hval; |
325 | 50.4k | uint32_t seq; |
326 | 50.4k | uint8_t copy; |
327 | | // Make a tradeoff between testing too much and too little |
328 | 50.4k | uint16_t limit = (maxlen > hashlen) ? hashlen : maxlen; |
329 | 50.4k | uint8_t* ip_bound = ibase + limit - 1; |
330 | 50.4k | uint8_t* ip_limit = ibase + limit - 12; |
331 | | |
332 | | // Initialize the hash table to distances of 0 |
333 | 50.4k | memset(htab, 0, hashlen * sizeof(uint32_t)); |
334 | | |
335 | | /* we start with literal copy */ |
336 | 50.4k | copy = 4; |
337 | 50.4k | oc += 5; |
338 | | |
339 | | /* main loop */ |
340 | 24.9M | while (BLOSCLZ_LIKELY(ip < ip_limit)) { |
341 | 24.8M | const uint8_t* ref; |
342 | 24.8M | unsigned distance; |
343 | 24.8M | uint8_t* anchor = ip; /* comparison starting-point */ |
344 | | |
345 | | /* find potential match */ |
346 | 24.8M | seq = BLOSCLZ_READU32(ip); |
347 | 24.8M | HASH_FUNCTION(hval, seq, hashlog) |
348 | 24.8M | ref = ibase + htab[hval]; |
349 | | |
350 | | /* calculate distance to the match */ |
351 | 24.8M | distance = (unsigned int)(anchor - ref); |
352 | | |
353 | | /* update hash table */ |
354 | 24.8M | htab[hval] = (uint32_t) (anchor - ibase); |
355 | | |
356 | 24.8M | if (distance == 0 || (distance >= MAX_FARDISTANCE)) { |
357 | 50.2k | LITERAL2(ip, anchor, copy) |
358 | 50.2k | continue; |
359 | 50.2k | } |
360 | | |
361 | | /* is this a match? check the first 4 bytes */ |
362 | 24.8M | if (BLOSCLZ_READU32(ref) == BLOSCLZ_READU32(ip)) { |
363 | 3.82M | ref += 4; |
364 | 3.82M | } |
365 | 20.9M | else { |
366 | | /* no luck, copy as a literal */ |
367 | 20.9M | LITERAL2(ip, anchor, copy) |
368 | 20.9M | continue; |
369 | 20.9M | } |
370 | | |
371 | | /* last matched byte */ |
372 | 3.82M | ip = anchor + 4; |
373 | | |
374 | | /* distance is biased */ |
375 | 3.82M | distance--; |
376 | | |
377 | | /* get runs or matches; zero distance means a run */ |
378 | 3.82M | ip = get_run_or_match(ip, ip_bound, ref, !distance); |
379 | | |
380 | 3.82M | ip -= ipshift; |
381 | 3.82M | int len = (int)(ip - anchor); |
382 | 3.82M | if (len < minlen) { |
383 | 2.96M | LITERAL2(ip, anchor, copy) |
384 | 2.96M | continue; |
385 | 2.96M | } |
386 | | |
387 | | /* if we haven't copied anything, adjust the output counter */ |
388 | 859k | if (!copy) |
389 | 202k | oc--; |
390 | | /* reset literal counter */ |
391 | 859k | copy = 0; |
392 | | |
393 | | /* encode the match */ |
394 | 859k | if (distance < MAX_DISTANCE) { |
395 | 854k | if (len >= 7) { |
396 | 472k | oc += ((len - 7) / 255) + 1; |
397 | 472k | } |
398 | 854k | oc += 2; |
399 | 854k | } |
400 | 5.47k | else { |
401 | | /* far away, but not yet in the another galaxy... */ |
402 | 5.47k | if (len >= 7) { |
403 | 3.52k | oc += ((len - 7) / 255) + 1; |
404 | 3.52k | } |
405 | 5.47k | oc += 4; |
406 | 5.47k | } |
407 | | |
408 | | /* update the hash at match boundary */ |
409 | 859k | seq = BLOSCLZ_READU32(ip); |
410 | 859k | HASH_FUNCTION(hval, seq, hashlog) |
411 | 859k | htab[hval] = (uint32_t)(ip++ - ibase); |
412 | 859k | ip++; |
413 | | /* assuming literal copy */ |
414 | 859k | oc++; |
415 | 859k | } |
416 | | |
417 | 50.4k | double ic = (double)(ip - ibase); |
418 | 50.4k | return ic / (double)oc; |
419 | 50.4k | } |
420 | | |
421 | | |
422 | | int blosclz_compress(const int clevel, const void* input, int length, |
423 | 50.4k | void* output, int maxout, blosc2_context* ctx) { |
424 | 50.4k | BLOSC_UNUSED_PARAM(ctx); |
425 | 50.4k | uint8_t* ibase = (uint8_t*)input; |
426 | 50.4k | uint32_t htab[1U << (uint8_t)HASH_LOG]; |
427 | | |
428 | | /* When we go back in a match (shift), we obtain quite different compression properties. |
429 | | * It looks like 4 is more useful in combination with bitshuffle and small typesizes |
430 | | * Fallback to 4 because it provides more consistent results for large cratios. |
431 | | * UPDATE: new experiments show that using a value of 3 is a bit better, at least for ERA5. |
432 | | * UPDATE 2: go back to 4, as they seem to provide better cratios in general. |
433 | | * |
434 | | * In this block we also check cratios for the beginning of the buffers and |
435 | | * eventually discard those that are small (take too long to decompress). |
436 | | * This process is called _entropy probing_. |
437 | | */ |
438 | 50.4k | unsigned ipshift = 4; |
439 | | // Minimum lengths for encoding (normally it is good to match the shift value) |
440 | 50.4k | unsigned minlen = 4; |
441 | | |
442 | 50.4k | uint8_t hashlog_[10] = {0, HASH_LOG - 2, HASH_LOG - 1, HASH_LOG, HASH_LOG, |
443 | 50.4k | HASH_LOG, HASH_LOG, HASH_LOG, HASH_LOG, HASH_LOG}; |
444 | 50.4k | uint8_t hashlog = hashlog_[clevel]; |
445 | | |
446 | | // Experiments say that checking 1/4 of the buffer is enough to figure out approx cratio |
447 | | // UPDATE: new experiments with ERA5 datasets (float32) say that checking the whole buffer |
448 | | // is better (specially when combined with bitshuffle). |
449 | | // The loss in speed for checking the whole buffer is pretty negligible too. |
450 | 50.4k | int maxlen = length; |
451 | 50.4k | if (clevel < 2) { |
452 | 36.4k | maxlen /= 8; |
453 | 36.4k | } |
454 | 13.9k | else if (clevel < 4) { |
455 | 2.33k | maxlen /= 4; |
456 | 2.33k | } |
457 | 11.6k | else if (clevel < 7) { |
458 | 4.03k | maxlen /= 2; |
459 | 4.03k | } |
460 | | // Start probing somewhere inside the buffer |
461 | 50.4k | int shift = length - maxlen; |
462 | | // Actual entropy probing! |
463 | 50.4k | double cratio = get_cratio(ibase + shift, maxlen, minlen, ipshift, htab, hashlog); |
464 | | // discard probes with small compression ratios (too expensive) |
465 | 50.4k | double cratio_[10] = {0, 2, 1.5, 1.2, 1.2, 1.2, 1.2, 1.15, 1.1, 1.0}; |
466 | 50.4k | if (cratio < cratio_[clevel]) { |
467 | 16.6k | goto out; |
468 | 16.6k | } |
469 | | |
470 | 33.7k | uint8_t* ip = ibase; |
471 | 33.7k | uint8_t* ip_bound = ibase + length - 1; |
472 | 33.7k | uint8_t* ip_limit = ibase + length - 12; |
473 | 33.7k | uint8_t* op = (uint8_t*)output; |
474 | 33.7k | const uint8_t* op_limit = op + maxout; |
475 | 33.7k | uint32_t seq; |
476 | 33.7k | uint8_t copy; |
477 | 33.7k | uint32_t hval; |
478 | | |
479 | | /* input and output buffer cannot be less than 16 and 66 bytes or we can get into trouble */ |
480 | 33.7k | if (length < 16 || maxout < 66) { |
481 | 187 | return 0; |
482 | 187 | } |
483 | | |
484 | | // Initialize the hash table |
485 | 33.5k | memset(htab, 0, (1U << hashlog) * sizeof(uint32_t)); |
486 | | |
487 | | /* we start with literal copy */ |
488 | 33.5k | copy = 4; |
489 | 33.5k | *op++ = MAX_COPY - 1; |
490 | 33.5k | *op++ = *ip++; |
491 | 33.5k | *op++ = *ip++; |
492 | 33.5k | *op++ = *ip++; |
493 | 33.5k | *op++ = *ip++; |
494 | | |
495 | | /* main loop */ |
496 | 20.5M | while (BLOSCLZ_LIKELY(ip < ip_limit)) { |
497 | 20.4M | const uint8_t* ref; |
498 | 20.4M | unsigned distance; |
499 | 20.4M | uint8_t* anchor = ip; /* comparison starting-point */ |
500 | | |
501 | | /* find potential match */ |
502 | 20.4M | seq = BLOSCLZ_READU32(ip); |
503 | 20.4M | HASH_FUNCTION(hval, seq, hashlog) |
504 | 20.4M | ref = ibase + htab[hval]; |
505 | | |
506 | | /* calculate distance to the match */ |
507 | 20.4M | distance = (unsigned int)(anchor - ref); |
508 | | |
509 | | /* update hash table */ |
510 | 20.4M | htab[hval] = (uint32_t) (anchor - ibase); |
511 | | |
512 | 20.4M | if (distance == 0 || (distance >= MAX_FARDISTANCE)) { |
513 | 52.8k | LITERAL(ip, op, op_limit, anchor, copy) |
514 | 0 | continue; |
515 | 52.8k | } |
516 | | |
517 | | /* is this a match? check the first 4 bytes */ |
518 | 20.4M | if (BLOSCLZ_UNLIKELY(BLOSCLZ_READU32(ref) == BLOSCLZ_READU32(ip))) { |
519 | 5.71M | ref += 4; |
520 | 14.7M | } else { |
521 | | /* no luck, copy as a literal */ |
522 | 14.7M | LITERAL(ip, op, op_limit, anchor, copy) |
523 | 0 | continue; |
524 | 14.7M | } |
525 | | |
526 | | /* last matched byte */ |
527 | 5.71M | ip = anchor + 4; |
528 | | |
529 | | /* distance is biased */ |
530 | 5.71M | distance--; |
531 | | |
532 | | /* get runs or matches; zero distance means a run */ |
533 | 5.71M | ip = get_run_or_match(ip, ip_bound, ref, !distance); |
534 | | |
535 | | /* length is biased, '1' means a match of 3 bytes */ |
536 | 5.71M | ip -= ipshift; |
537 | | |
538 | 5.71M | unsigned len = (int)(ip - anchor); |
539 | | |
540 | | // Encoding short lengths is expensive during decompression |
541 | 5.71M | if (len < minlen || (len <= 5 && distance >= MAX_DISTANCE)) { |
542 | 4.24M | LITERAL(ip, op, op_limit, anchor, copy) |
543 | 0 | continue; |
544 | 4.24M | } |
545 | | |
546 | | /* if we have copied something, adjust the copy count */ |
547 | 1.46M | if (copy) |
548 | | /* copy is biased, '0' means 1 byte copy */ |
549 | 1.03M | *(op - copy - 1) = (uint8_t)(copy - 1); |
550 | 431k | else |
551 | | /* back, to overwrite the copy count */ |
552 | 431k | op--; |
553 | | /* reset literal counter */ |
554 | 1.46M | copy = 0; |
555 | | |
556 | | /* encode the match */ |
557 | 1.46M | if (distance < MAX_DISTANCE) { |
558 | 1.42M | if (len < 7) { |
559 | 561k | MATCH_SHORT(op, op_limit, len, distance) |
560 | 866k | } else { |
561 | 2.60M | MATCH_LONG(op, op_limit, len, distance) |
562 | 2.60M | } |
563 | 1.42M | } else { |
564 | | /* far away, but not yet in the another galaxy... */ |
565 | 40.4k | distance -= MAX_DISTANCE; |
566 | 40.4k | if (len < 7) { |
567 | 3.99k | MATCH_SHORT_FAR(op, op_limit, len, distance) |
568 | 36.4k | } else { |
569 | 109k | MATCH_LONG_FAR(op, op_limit, len, distance) |
570 | 109k | } |
571 | 40.4k | } |
572 | | |
573 | | /* update the hash at match boundary */ |
574 | 1.46M | seq = BLOSCLZ_READU32(ip); |
575 | 1.46M | HASH_FUNCTION(hval, seq, hashlog) |
576 | 1.46M | htab[hval] = (uint32_t) (ip++ - ibase); |
577 | 1.46M | if (clevel == 9) { |
578 | | // In some situations, including a second hash proves to be useful, |
579 | | // but not in others. Activating here in max clevel only. |
580 | 441k | seq >>= 8U; |
581 | 441k | HASH_FUNCTION(hval, seq, hashlog) |
582 | 441k | htab[hval] = (uint32_t) (ip++ - ibase); |
583 | 441k | } |
584 | 1.02M | else { |
585 | 1.02M | ip++; |
586 | 1.02M | } |
587 | | |
588 | 1.46M | if (BLOSCLZ_UNLIKELY(op_limit - op < 1)) |
589 | 203 | goto out; |
590 | | |
591 | | /* assuming literal copy */ |
592 | 1.46M | *op++ = MAX_COPY - 1; |
593 | 1.46M | } |
594 | | |
595 | | /* left-over as literal copy */ |
596 | 195k | while (BLOSCLZ_UNLIKELY(ip <= ip_bound)) { |
597 | 163k | if (BLOSCLZ_UNLIKELY(op_limit - op < 2)) goto out; |
598 | 163k | *op++ = *ip++; |
599 | 163k | copy++; |
600 | 163k | if (BLOSCLZ_UNLIKELY(copy == MAX_COPY)) { |
601 | 845 | copy = 0; |
602 | 845 | *op++ = MAX_COPY - 1; |
603 | 845 | } |
604 | 163k | } |
605 | | |
606 | | /* if we have copied something, adjust the copy length */ |
607 | 32.2k | if (copy) |
608 | 31.9k | *(op - copy - 1) = (uint8_t)(copy - 1); |
609 | 305 | else |
610 | 305 | op--; |
611 | | |
612 | | /* marker for blosclz */ |
613 | 32.2k | *(uint8_t*)output |= (1U << 5U); |
614 | | |
615 | 32.2k | return (int)(op - (uint8_t*)output); |
616 | | |
617 | 17.9k | out: |
618 | 17.9k | return 0; |
619 | 32.5k | } |
620 | | |
621 | | // See https://habr.com/en/company/yandex/blog/457612/ |
622 | | #if defined(__AVX2__) |
623 | | |
624 | | #if defined(_MSC_VER) |
625 | | #define ALIGNED_(x) __declspec(align(x)) |
626 | | #else |
627 | | #if defined(__GNUC__) |
628 | | #define ALIGNED_(x) __attribute__ ((aligned(x))) |
629 | | #endif |
630 | | #endif |
631 | | #define ALIGNED_TYPE_(t, x) t ALIGNED_(x) |
632 | | |
633 | | static unsigned char* copy_match_16(unsigned char *op, const unsigned char *match, int32_t len) |
634 | | { |
635 | | size_t offset = op - match; |
636 | | while (len >= 16) { |
637 | | |
638 | | static const ALIGNED_TYPE_(uint8_t, 16) masks[] = |
639 | | { |
640 | | 0, 1, 2, 1, 4, 1, 4, 2, 8, 7, 6, 5, 4, 3, 2, 1, // offset = 0, not used as mask, but for shift |
641 | | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // offset = 1 |
642 | | 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, |
643 | | 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, |
644 | | 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, |
645 | | 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, |
646 | | 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, |
647 | | 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, |
648 | | 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, |
649 | | 0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 1, 2, 3, 4, 5, 6, |
650 | | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, |
651 | | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 1, 2, 3, 4, |
652 | | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 0, 1, 2, 3, |
653 | | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0, 1, 2, |
654 | | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 0, 1, |
655 | | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 0, |
656 | | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // offset = 16 |
657 | | }; |
658 | | |
659 | | _mm_storeu_si128((__m128i *)(op), |
660 | | _mm_shuffle_epi8(_mm_loadu_si128((const __m128i *)(match)), |
661 | | _mm_load_si128((const __m128i *)(masks) + offset))); |
662 | | |
663 | | match += masks[offset]; |
664 | | |
665 | | op += 16; |
666 | | len -= 16; |
667 | | } |
668 | | // Deal with remainders |
669 | | for (; len > 0; len--) { |
670 | | *op++ = *match++; |
671 | | } |
672 | | return op; |
673 | | } |
674 | | #endif |
675 | | |
676 | | // LZ4 wildCopy which can reach excellent copy bandwidth (even if insecure) |
677 | 503k | static inline void wild_copy(uint8_t *out, const uint8_t* from, uint8_t* end) { |
678 | 503k | uint8_t* d = out; |
679 | 503k | const uint8_t* s = from; |
680 | 503k | uint8_t* const e = end; |
681 | | |
682 | 2.66M | do { memcpy(d,s,8); d+=8; s+=8; } while (d<e); |
683 | 503k | } |
684 | | |
685 | 11.0k | int blosclz_decompress(const void* input, int length, void* output, int maxout) { |
686 | 11.0k | const uint8_t* ip = (const uint8_t*)input; |
687 | 11.0k | const uint8_t* ip_limit = ip + length; |
688 | 11.0k | uint8_t* op = (uint8_t*)output; |
689 | 11.0k | uint32_t ctrl; |
690 | 11.0k | uint8_t* op_limit = op + maxout; |
691 | 11.0k | if (BLOSCLZ_UNLIKELY(length == 0)) { |
692 | 0 | return 0; |
693 | 0 | } |
694 | 11.0k | ctrl = (*ip++) & 31U; |
695 | | |
696 | 2.11M | while (1) { |
697 | 2.11M | if (ctrl >= 32) { |
698 | | // match |
699 | 702k | int32_t len = (int32_t)(ctrl >> 5U) - 1 ; |
700 | 702k | int32_t ofs = (int32_t)(ctrl & 31U) << 8U; |
701 | 702k | uint8_t code; |
702 | 702k | const uint8_t* ref = op - ofs; |
703 | | |
704 | 702k | if (len == 7 - 1) { |
705 | 444k | do { |
706 | 444k | if (BLOSCLZ_UNLIKELY(ip + 1 >= ip_limit)) { |
707 | 24 | return 0; |
708 | 24 | } |
709 | 444k | code = *ip++; |
710 | 444k | len += code; |
711 | 444k | if (BLOSCLZ_UNLIKELY(len > maxout)) { |
712 | 30 | return 0; |
713 | 30 | } |
714 | 444k | } while (code == 255); |
715 | 375k | } |
716 | 326k | else { |
717 | 326k | if (BLOSCLZ_UNLIKELY(ip + 1 >= ip_limit)) { |
718 | 52 | return 0; |
719 | 52 | } |
720 | 326k | } |
721 | 702k | code = *ip++; |
722 | 702k | len += 3; |
723 | 702k | ref -= code; |
724 | | |
725 | | /* match from 16-bit distance */ |
726 | 702k | if (BLOSCLZ_UNLIKELY(code == 255)) { |
727 | 61.4k | if (ofs == (31U << 8U)) { |
728 | 28.3k | if (ip + 1 >= ip_limit) { |
729 | 2 | return 0; |
730 | 2 | } |
731 | 28.3k | ofs = (*ip++) << 8U; |
732 | 28.3k | ofs += *ip++; |
733 | 28.3k | ref = op - ofs - MAX_DISTANCE; |
734 | 28.3k | } |
735 | 61.4k | } |
736 | | |
737 | 702k | if (BLOSCLZ_UNLIKELY(len > op_limit - op)) { |
738 | 115 | return 0; |
739 | 115 | } |
740 | | |
741 | 702k | if (BLOSCLZ_UNLIKELY(ref <= (uint8_t*)output)) { |
742 | 161 | return 0; |
743 | 161 | } |
744 | | |
745 | 701k | if (BLOSCLZ_UNLIKELY(ip >= ip_limit)) break; |
746 | 701k | ctrl = *ip++; |
747 | | |
748 | 701k | ref--; |
749 | 701k | if (ref == op - 1) { |
750 | | /* optimized copy for a run */ |
751 | 73.6k | memset(op, *ref, len); |
752 | 73.6k | op += len; |
753 | 73.6k | } |
754 | 628k | else if ((op - ref >= 8) && (op_limit - op >= len + 8)) { |
755 | | // copy with an overlap not larger than 8 |
756 | 503k | wild_copy(op, ref, op + len); |
757 | 503k | op += len; |
758 | 503k | } |
759 | 124k | else { |
760 | | // general copy with any overlap |
761 | | #if 0 && defined(__AVX2__) |
762 | | if (op - ref <= 16) { |
763 | | // This is not faster on a combination of compilers (clang, gcc, icc) or machines, but |
764 | | // it is not too slower either. |
765 | | op = copy_match_16(op, ref, len); |
766 | | } |
767 | | else { |
768 | | #endif |
769 | 124k | op = copy_match(op, ref, (unsigned) len); |
770 | | #if 0 && defined(__AVX2__) |
771 | | } |
772 | | #endif |
773 | 124k | } |
774 | 701k | } |
775 | 1.41M | else { |
776 | | // literal |
777 | 1.41M | ctrl++; |
778 | 1.41M | if (BLOSCLZ_UNLIKELY(ctrl > op_limit - op)) { |
779 | 81 | return 0; |
780 | 81 | } |
781 | 1.41M | if (BLOSCLZ_UNLIKELY(ctrl > ip_limit - ip)) { |
782 | 35 | return 0; |
783 | 35 | } |
784 | | |
785 | 1.41M | memcpy(op, ip, ctrl); op += ctrl; ip += ctrl; |
786 | | // On GCC-6, fastcopy this is still faster than plain memcpy |
787 | | // However, using recent CLANG/LLVM 9.0, there is almost no difference |
788 | | // in performance. |
789 | | // And starting on CLANG/LLVM 10 and GCC 9, memcpy is generally faster. |
790 | | // op = fastcopy(op, ip, (unsigned) ctrl); ip += ctrl; |
791 | | |
792 | 1.41M | if (BLOSCLZ_UNLIKELY(ip >= ip_limit)) break; |
793 | 1.40M | ctrl = *ip++; |
794 | 1.40M | } |
795 | 2.11M | } |
796 | | |
797 | 10.5k | return (int)(op - (uint8_t*)output); |
798 | 11.0k | } |