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