Home | History | Annotate | Line # | Download | only in compress
      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