xref: /linux/lib/zstd/compress/zstd_preSplit.c (revision e61f33273ca755b3e2ebee4520a76097199dc7a8)
1*65d1f550SNick Terrell // SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause
2*65d1f550SNick Terrell /*
3*65d1f550SNick Terrell  * Copyright (c) Meta Platforms, Inc. and affiliates.
4*65d1f550SNick Terrell  * All rights reserved.
5*65d1f550SNick Terrell  *
6*65d1f550SNick Terrell  * This source code is licensed under both the BSD-style license (found in the
7*65d1f550SNick Terrell  * LICENSE file in the root directory of this source tree) and the GPLv2 (found
8*65d1f550SNick Terrell  * in the COPYING file in the root directory of this source tree).
9*65d1f550SNick Terrell  * You may select, at your option, one of the above-listed licenses.
10*65d1f550SNick Terrell  */
11*65d1f550SNick Terrell 
12*65d1f550SNick Terrell #include "../common/compiler.h" /* ZSTD_ALIGNOF */
13*65d1f550SNick Terrell #include "../common/mem.h" /* S64 */
14*65d1f550SNick Terrell #include "../common/zstd_deps.h" /* ZSTD_memset */
15*65d1f550SNick Terrell #include "../common/zstd_internal.h" /* ZSTD_STATIC_ASSERT */
16*65d1f550SNick Terrell #include "hist.h" /* HIST_add */
17*65d1f550SNick Terrell #include "zstd_preSplit.h"
18*65d1f550SNick Terrell 
19*65d1f550SNick Terrell 
20*65d1f550SNick Terrell #define BLOCKSIZE_MIN 3500
21*65d1f550SNick Terrell #define THRESHOLD_PENALTY_RATE 16
22*65d1f550SNick Terrell #define THRESHOLD_BASE (THRESHOLD_PENALTY_RATE - 2)
23*65d1f550SNick Terrell #define THRESHOLD_PENALTY 3
24*65d1f550SNick Terrell 
25*65d1f550SNick Terrell #define HASHLENGTH 2
26*65d1f550SNick Terrell #define HASHLOG_MAX 10
27*65d1f550SNick Terrell #define HASHTABLESIZE (1 << HASHLOG_MAX)
28*65d1f550SNick Terrell #define HASHMASK (HASHTABLESIZE - 1)
29*65d1f550SNick Terrell #define KNUTH 0x9e3779b9
30*65d1f550SNick Terrell 
31*65d1f550SNick Terrell /* for hashLog > 8, hash 2 bytes.
32*65d1f550SNick Terrell  * for hashLog == 8, just take the byte, no hashing.
33*65d1f550SNick Terrell  * The speed of this method relies on compile-time constant propagation */
hash2(const void * p,unsigned hashLog)34*65d1f550SNick Terrell FORCE_INLINE_TEMPLATE unsigned hash2(const void *p, unsigned hashLog)
35*65d1f550SNick Terrell {
36*65d1f550SNick Terrell     assert(hashLog >= 8);
37*65d1f550SNick Terrell     if (hashLog == 8) return (U32)((const BYTE*)p)[0];
38*65d1f550SNick Terrell     assert(hashLog <= HASHLOG_MAX);
39*65d1f550SNick Terrell     return (U32)(MEM_read16(p)) * KNUTH >> (32 - hashLog);
40*65d1f550SNick Terrell }
41*65d1f550SNick Terrell 
42*65d1f550SNick Terrell 
43*65d1f550SNick Terrell typedef struct {
44*65d1f550SNick Terrell   unsigned events[HASHTABLESIZE];
45*65d1f550SNick Terrell   size_t nbEvents;
46*65d1f550SNick Terrell } Fingerprint;
47*65d1f550SNick Terrell typedef struct {
48*65d1f550SNick Terrell     Fingerprint pastEvents;
49*65d1f550SNick Terrell     Fingerprint newEvents;
50*65d1f550SNick Terrell } FPStats;
51*65d1f550SNick Terrell 
initStats(FPStats * fpstats)52*65d1f550SNick Terrell static void initStats(FPStats* fpstats)
53*65d1f550SNick Terrell {
54*65d1f550SNick Terrell     ZSTD_memset(fpstats, 0, sizeof(FPStats));
55*65d1f550SNick Terrell }
56*65d1f550SNick Terrell 
57*65d1f550SNick Terrell FORCE_INLINE_TEMPLATE void
addEvents_generic(Fingerprint * fp,const void * src,size_t srcSize,size_t samplingRate,unsigned hashLog)58*65d1f550SNick Terrell addEvents_generic(Fingerprint* fp, const void* src, size_t srcSize, size_t samplingRate, unsigned hashLog)
59*65d1f550SNick Terrell {
60*65d1f550SNick Terrell     const char* p = (const char*)src;
61*65d1f550SNick Terrell     size_t limit = srcSize - HASHLENGTH + 1;
62*65d1f550SNick Terrell     size_t n;
63*65d1f550SNick Terrell     assert(srcSize >= HASHLENGTH);
64*65d1f550SNick Terrell     for (n = 0; n < limit; n+=samplingRate) {
65*65d1f550SNick Terrell         fp->events[hash2(p+n, hashLog)]++;
66*65d1f550SNick Terrell     }
67*65d1f550SNick Terrell     fp->nbEvents += limit/samplingRate;
68*65d1f550SNick Terrell }
69*65d1f550SNick Terrell 
70*65d1f550SNick Terrell FORCE_INLINE_TEMPLATE void
recordFingerprint_generic(Fingerprint * fp,const void * src,size_t srcSize,size_t samplingRate,unsigned hashLog)71*65d1f550SNick Terrell recordFingerprint_generic(Fingerprint* fp, const void* src, size_t srcSize, size_t samplingRate, unsigned hashLog)
72*65d1f550SNick Terrell {
73*65d1f550SNick Terrell     ZSTD_memset(fp, 0, sizeof(unsigned) * ((size_t)1 << hashLog));
74*65d1f550SNick Terrell     fp->nbEvents = 0;
75*65d1f550SNick Terrell     addEvents_generic(fp, src, srcSize, samplingRate, hashLog);
76*65d1f550SNick Terrell }
77*65d1f550SNick Terrell 
78*65d1f550SNick Terrell typedef void (*RecordEvents_f)(Fingerprint* fp, const void* src, size_t srcSize);
79*65d1f550SNick Terrell 
80*65d1f550SNick Terrell #define FP_RECORD(_rate) ZSTD_recordFingerprint_##_rate
81*65d1f550SNick Terrell 
82*65d1f550SNick Terrell #define ZSTD_GEN_RECORD_FINGERPRINT(_rate, _hSize)                                 \
83*65d1f550SNick Terrell     static void FP_RECORD(_rate)(Fingerprint* fp, const void* src, size_t srcSize) \
84*65d1f550SNick Terrell     {                                                                              \
85*65d1f550SNick Terrell         recordFingerprint_generic(fp, src, srcSize, _rate, _hSize);                \
86*65d1f550SNick Terrell     }
87*65d1f550SNick Terrell 
88*65d1f550SNick Terrell ZSTD_GEN_RECORD_FINGERPRINT(1, 10)
89*65d1f550SNick Terrell ZSTD_GEN_RECORD_FINGERPRINT(5, 10)
90*65d1f550SNick Terrell ZSTD_GEN_RECORD_FINGERPRINT(11, 9)
91*65d1f550SNick Terrell ZSTD_GEN_RECORD_FINGERPRINT(43, 8)
92*65d1f550SNick Terrell 
93*65d1f550SNick Terrell 
abs64(S64 s64)94*65d1f550SNick Terrell static U64 abs64(S64 s64) { return (U64)((s64 < 0) ? -s64 : s64); }
95*65d1f550SNick Terrell 
fpDistance(const Fingerprint * fp1,const Fingerprint * fp2,unsigned hashLog)96*65d1f550SNick Terrell static U64 fpDistance(const Fingerprint* fp1, const Fingerprint* fp2, unsigned hashLog)
97*65d1f550SNick Terrell {
98*65d1f550SNick Terrell     U64 distance = 0;
99*65d1f550SNick Terrell     size_t n;
100*65d1f550SNick Terrell     assert(hashLog <= HASHLOG_MAX);
101*65d1f550SNick Terrell     for (n = 0; n < ((size_t)1 << hashLog); n++) {
102*65d1f550SNick Terrell         distance +=
103*65d1f550SNick Terrell             abs64((S64)fp1->events[n] * (S64)fp2->nbEvents - (S64)fp2->events[n] * (S64)fp1->nbEvents);
104*65d1f550SNick Terrell     }
105*65d1f550SNick Terrell     return distance;
106*65d1f550SNick Terrell }
107*65d1f550SNick Terrell 
108*65d1f550SNick Terrell /* Compare newEvents with pastEvents
109*65d1f550SNick Terrell  * return 1 when considered "too different"
110*65d1f550SNick Terrell  */
compareFingerprints(const Fingerprint * ref,const Fingerprint * newfp,int penalty,unsigned hashLog)111*65d1f550SNick Terrell static int compareFingerprints(const Fingerprint* ref,
112*65d1f550SNick Terrell                             const Fingerprint* newfp,
113*65d1f550SNick Terrell                             int penalty,
114*65d1f550SNick Terrell                             unsigned hashLog)
115*65d1f550SNick Terrell {
116*65d1f550SNick Terrell     assert(ref->nbEvents > 0);
117*65d1f550SNick Terrell     assert(newfp->nbEvents > 0);
118*65d1f550SNick Terrell     {   U64 p50 = (U64)ref->nbEvents * (U64)newfp->nbEvents;
119*65d1f550SNick Terrell         U64 deviation = fpDistance(ref, newfp, hashLog);
120*65d1f550SNick Terrell         U64 threshold = p50 * (U64)(THRESHOLD_BASE + penalty) / THRESHOLD_PENALTY_RATE;
121*65d1f550SNick Terrell         return deviation >= threshold;
122*65d1f550SNick Terrell     }
123*65d1f550SNick Terrell }
124*65d1f550SNick Terrell 
mergeEvents(Fingerprint * acc,const Fingerprint * newfp)125*65d1f550SNick Terrell static void mergeEvents(Fingerprint* acc, const Fingerprint* newfp)
126*65d1f550SNick Terrell {
127*65d1f550SNick Terrell     size_t n;
128*65d1f550SNick Terrell     for (n = 0; n < HASHTABLESIZE; n++) {
129*65d1f550SNick Terrell         acc->events[n] += newfp->events[n];
130*65d1f550SNick Terrell     }
131*65d1f550SNick Terrell     acc->nbEvents += newfp->nbEvents;
132*65d1f550SNick Terrell }
133*65d1f550SNick Terrell 
flushEvents(FPStats * fpstats)134*65d1f550SNick Terrell static void flushEvents(FPStats* fpstats)
135*65d1f550SNick Terrell {
136*65d1f550SNick Terrell     size_t n;
137*65d1f550SNick Terrell     for (n = 0; n < HASHTABLESIZE; n++) {
138*65d1f550SNick Terrell         fpstats->pastEvents.events[n] = fpstats->newEvents.events[n];
139*65d1f550SNick Terrell     }
140*65d1f550SNick Terrell     fpstats->pastEvents.nbEvents = fpstats->newEvents.nbEvents;
141*65d1f550SNick Terrell     ZSTD_memset(&fpstats->newEvents, 0, sizeof(fpstats->newEvents));
142*65d1f550SNick Terrell }
143*65d1f550SNick Terrell 
removeEvents(Fingerprint * acc,const Fingerprint * slice)144*65d1f550SNick Terrell static void removeEvents(Fingerprint* acc, const Fingerprint* slice)
145*65d1f550SNick Terrell {
146*65d1f550SNick Terrell     size_t n;
147*65d1f550SNick Terrell     for (n = 0; n < HASHTABLESIZE; n++) {
148*65d1f550SNick Terrell         assert(acc->events[n] >= slice->events[n]);
149*65d1f550SNick Terrell         acc->events[n] -= slice->events[n];
150*65d1f550SNick Terrell     }
151*65d1f550SNick Terrell     acc->nbEvents -= slice->nbEvents;
152*65d1f550SNick Terrell }
153*65d1f550SNick Terrell 
154*65d1f550SNick Terrell #define CHUNKSIZE (8 << 10)
ZSTD_splitBlock_byChunks(const void * blockStart,size_t blockSize,int level,void * workspace,size_t wkspSize)155*65d1f550SNick Terrell static size_t ZSTD_splitBlock_byChunks(const void* blockStart, size_t blockSize,
156*65d1f550SNick Terrell                         int level,
157*65d1f550SNick Terrell                         void* workspace, size_t wkspSize)
158*65d1f550SNick Terrell {
159*65d1f550SNick Terrell     static const RecordEvents_f records_fs[] = {
160*65d1f550SNick Terrell         FP_RECORD(43), FP_RECORD(11), FP_RECORD(5), FP_RECORD(1)
161*65d1f550SNick Terrell     };
162*65d1f550SNick Terrell     static const unsigned hashParams[] = { 8, 9, 10, 10 };
163*65d1f550SNick Terrell     const RecordEvents_f record_f = (assert(0<=level && level<=3), records_fs[level]);
164*65d1f550SNick Terrell     FPStats* const fpstats = (FPStats*)workspace;
165*65d1f550SNick Terrell     const char* p = (const char*)blockStart;
166*65d1f550SNick Terrell     int penalty = THRESHOLD_PENALTY;
167*65d1f550SNick Terrell     size_t pos = 0;
168*65d1f550SNick Terrell     assert(blockSize == (128 << 10));
169*65d1f550SNick Terrell     assert(workspace != NULL);
170*65d1f550SNick Terrell     assert((size_t)workspace % ZSTD_ALIGNOF(FPStats) == 0);
171*65d1f550SNick Terrell     ZSTD_STATIC_ASSERT(ZSTD_SLIPBLOCK_WORKSPACESIZE >= sizeof(FPStats));
172*65d1f550SNick Terrell     assert(wkspSize >= sizeof(FPStats)); (void)wkspSize;
173*65d1f550SNick Terrell 
174*65d1f550SNick Terrell     initStats(fpstats);
175*65d1f550SNick Terrell     record_f(&fpstats->pastEvents, p, CHUNKSIZE);
176*65d1f550SNick Terrell     for (pos = CHUNKSIZE; pos <= blockSize - CHUNKSIZE; pos += CHUNKSIZE) {
177*65d1f550SNick Terrell         record_f(&fpstats->newEvents, p + pos, CHUNKSIZE);
178*65d1f550SNick Terrell         if (compareFingerprints(&fpstats->pastEvents, &fpstats->newEvents, penalty, hashParams[level])) {
179*65d1f550SNick Terrell             return pos;
180*65d1f550SNick Terrell         } else {
181*65d1f550SNick Terrell             mergeEvents(&fpstats->pastEvents, &fpstats->newEvents);
182*65d1f550SNick Terrell             if (penalty > 0) penalty--;
183*65d1f550SNick Terrell         }
184*65d1f550SNick Terrell     }
185*65d1f550SNick Terrell     assert(pos == blockSize);
186*65d1f550SNick Terrell     return blockSize;
187*65d1f550SNick Terrell     (void)flushEvents; (void)removeEvents;
188*65d1f550SNick Terrell }
189*65d1f550SNick Terrell 
190*65d1f550SNick Terrell /* ZSTD_splitBlock_fromBorders(): very fast strategy :
191*65d1f550SNick Terrell  * compare fingerprint from beginning and end of the block,
192*65d1f550SNick Terrell  * derive from their difference if it's preferable to split in the middle,
193*65d1f550SNick Terrell  * repeat the process a second time, for finer grained decision.
194*65d1f550SNick Terrell  * 3 times did not brought improvements, so I stopped at 2.
195*65d1f550SNick Terrell  * Benefits are good enough for a cheap heuristic.
196*65d1f550SNick Terrell  * More accurate splitting saves more, but speed impact is also more perceptible.
197*65d1f550SNick Terrell  * For better accuracy, use more elaborate variant *_byChunks.
198*65d1f550SNick Terrell  */
ZSTD_splitBlock_fromBorders(const void * blockStart,size_t blockSize,void * workspace,size_t wkspSize)199*65d1f550SNick Terrell static size_t ZSTD_splitBlock_fromBorders(const void* blockStart, size_t blockSize,
200*65d1f550SNick Terrell                         void* workspace, size_t wkspSize)
201*65d1f550SNick Terrell {
202*65d1f550SNick Terrell #define SEGMENT_SIZE 512
203*65d1f550SNick Terrell     FPStats* const fpstats = (FPStats*)workspace;
204*65d1f550SNick Terrell     Fingerprint* middleEvents = (Fingerprint*)(void*)((char*)workspace + 512 * sizeof(unsigned));
205*65d1f550SNick Terrell     assert(blockSize == (128 << 10));
206*65d1f550SNick Terrell     assert(workspace != NULL);
207*65d1f550SNick Terrell     assert((size_t)workspace % ZSTD_ALIGNOF(FPStats) == 0);
208*65d1f550SNick Terrell     ZSTD_STATIC_ASSERT(ZSTD_SLIPBLOCK_WORKSPACESIZE >= sizeof(FPStats));
209*65d1f550SNick Terrell     assert(wkspSize >= sizeof(FPStats)); (void)wkspSize;
210*65d1f550SNick Terrell 
211*65d1f550SNick Terrell     initStats(fpstats);
212*65d1f550SNick Terrell     HIST_add(fpstats->pastEvents.events, blockStart, SEGMENT_SIZE);
213*65d1f550SNick Terrell     HIST_add(fpstats->newEvents.events, (const char*)blockStart + blockSize - SEGMENT_SIZE, SEGMENT_SIZE);
214*65d1f550SNick Terrell     fpstats->pastEvents.nbEvents = fpstats->newEvents.nbEvents = SEGMENT_SIZE;
215*65d1f550SNick Terrell     if (!compareFingerprints(&fpstats->pastEvents, &fpstats->newEvents, 0, 8))
216*65d1f550SNick Terrell         return blockSize;
217*65d1f550SNick Terrell 
218*65d1f550SNick Terrell     HIST_add(middleEvents->events, (const char*)blockStart + blockSize/2 - SEGMENT_SIZE/2, SEGMENT_SIZE);
219*65d1f550SNick Terrell     middleEvents->nbEvents = SEGMENT_SIZE;
220*65d1f550SNick Terrell     {   U64 const distFromBegin = fpDistance(&fpstats->pastEvents, middleEvents, 8);
221*65d1f550SNick Terrell         U64 const distFromEnd = fpDistance(&fpstats->newEvents, middleEvents, 8);
222*65d1f550SNick Terrell         U64 const minDistance = SEGMENT_SIZE * SEGMENT_SIZE / 3;
223*65d1f550SNick Terrell         if (abs64((S64)distFromBegin - (S64)distFromEnd) < minDistance)
224*65d1f550SNick Terrell             return 64 KB;
225*65d1f550SNick Terrell         return (distFromBegin > distFromEnd) ? 32 KB : 96 KB;
226*65d1f550SNick Terrell     }
227*65d1f550SNick Terrell }
228*65d1f550SNick Terrell 
ZSTD_splitBlock(const void * blockStart,size_t blockSize,int level,void * workspace,size_t wkspSize)229*65d1f550SNick Terrell size_t ZSTD_splitBlock(const void* blockStart, size_t blockSize,
230*65d1f550SNick Terrell                     int level,
231*65d1f550SNick Terrell                     void* workspace, size_t wkspSize)
232*65d1f550SNick Terrell {
233*65d1f550SNick Terrell     DEBUGLOG(6, "ZSTD_splitBlock (level=%i)", level);
234*65d1f550SNick Terrell     assert(0<=level && level<=4);
235*65d1f550SNick Terrell     if (level == 0)
236*65d1f550SNick Terrell         return ZSTD_splitBlock_fromBorders(blockStart, blockSize, workspace, wkspSize);
237*65d1f550SNick Terrell     /* level >= 1*/
238*65d1f550SNick Terrell     return ZSTD_splitBlock_byChunks(blockStart, blockSize, level-1, workspace, wkspSize);
239*65d1f550SNick Terrell }
240