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