Skip to content

Commit 91f4c23

Browse files
authored
Add salt into row hash (#3528 part 2) (#3533)
Part 2 of #3528 Adds hash salt that helps to avoid regressions where consecutive compressions use the same tag space with similar data (running zstd -b5e7 enwik8 -B128K reproduces this regression).
1 parent 9420bce commit 91f4c23

File tree

4 files changed

+104
-48
lines changed

4 files changed

+104
-48
lines changed

lib/common/bits.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,4 +172,29 @@ MEM_STATIC unsigned ZSTD_highbit32(U32 val) /* compress, dictBuilder, decodeCo
172172
return 31 - ZSTD_countLeadingZeros32(val);
173173
}
174174

175+
/* ZSTD_rotateRight_*():
176+
* Rotates a bitfield to the right by "count" bits.
177+
* https://en.wikipedia.org/w/index.php?title=Circular_shift&oldid=991635599#Implementing_circular_shifts
178+
*/
179+
MEM_STATIC
180+
U64 ZSTD_rotateRight_U64(U64 const value, U32 count) {
181+
assert(count < 64);
182+
count &= 0x3F; /* for fickle pattern recognition */
183+
return (value >> count) | (U64)(value << ((0U - count) & 0x3F));
184+
}
185+
186+
MEM_STATIC
187+
U32 ZSTD_rotateRight_U32(U32 const value, U32 count) {
188+
assert(count < 32);
189+
count &= 0x1F; /* for fickle pattern recognition */
190+
return (value >> count) | (U32)(value << ((0U - count) & 0x1F));
191+
}
192+
193+
MEM_STATIC
194+
U16 ZSTD_rotateRight_U16(U16 const value, U32 count) {
195+
assert(count < 16);
196+
count &= 0x0F; /* for fickle pattern recognition */
197+
return (value >> count) | (U16)(value << ((0U - count) & 0x0F));
198+
}
199+
175200
#endif /* ZSTD_BITS_H */

lib/compress/zstd_compress.c

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
#include "zstd_opt.h"
2828
#include "zstd_ldm.h"
2929
#include "zstd_compress_superblock.h"
30-
#include "../common/bits.h" /* ZSTD_highbit32 */
30+
#include "../common/bits.h" /* ZSTD_highbit32, ZSTD_rotateRight_U64 */
3131

3232
/* ***************************************************************
3333
* Tuning parameters
@@ -1907,6 +1907,19 @@ typedef enum {
19071907
ZSTD_resetTarget_CCtx
19081908
} ZSTD_resetTarget_e;
19091909

1910+
/* Mixes bits in a 64 bits in a value, based on XXH3_rrmxmx */
1911+
static U64 ZSTD_bitmix(U64 val, U64 len) {
1912+
val ^= ZSTD_rotateRight_U64(val, 49) ^ ZSTD_rotateRight_U64(val, 24);
1913+
val *= 0x9FB21C651E98DF25ULL;
1914+
val ^= (val >> 35) + len ;
1915+
val *= 0x9FB21C651E98DF25ULL;
1916+
return val ^ (val >> 28);
1917+
}
1918+
1919+
/* Mixes in the hashSalt and hashSaltEntropy to create a new hashSalt */
1920+
static void ZSTD_advanceHashSalt(ZSTD_matchState_t* ms) {
1921+
ms->hashSalt = ZSTD_bitmix(ms->hashSalt, 8) ^ ZSTD_bitmix((U64) ms->hashSaltEntropy, 4);
1922+
}
19101923

19111924
static size_t
19121925
ZSTD_reset_matchState(ZSTD_matchState_t* ms,
@@ -1958,7 +1971,17 @@ ZSTD_reset_matchState(ZSTD_matchState_t* ms,
19581971
if (ZSTD_rowMatchFinderUsed(cParams->strategy, useRowMatchFinder)) {
19591972
/* Row match finder needs an additional table of hashes ("tags") */
19601973
size_t const tagTableSize = hSize;
1961-
ms->tagTable = (BYTE*) ZSTD_cwksp_reserve_aligned_init_once(ws, tagTableSize);
1974+
/* We want to generate a new salt in case we reset a Cctx, but we always want to use
1975+
* 0 when we reset a Cdict */
1976+
if(forWho == ZSTD_resetTarget_CCtx) {
1977+
ms->tagTable = (BYTE*) ZSTD_cwksp_reserve_aligned_init_once(ws, tagTableSize);
1978+
ZSTD_advanceHashSalt(ms);
1979+
} else {
1980+
/* When we are not salting we want to always memset the memory */
1981+
ms->tagTable = (BYTE*) ZSTD_cwksp_reserve_aligned(ws, tagTableSize);
1982+
ZSTD_memset(ms->tagTable, 0, tagTableSize);
1983+
ms->hashSalt = 0;
1984+
}
19621985
{ /* Switch to 32-entry rows if searchLog is 5 (or more) */
19631986
U32 const rowLog = BOUNDED(4, cParams->searchLog, 6);
19641987
assert(cParams->hashLog >= rowLog);
@@ -2364,8 +2387,9 @@ static size_t ZSTD_resetCCtx_byCopyingCDict(ZSTD_CCtx* cctx,
23642387
if (ZSTD_rowMatchFinderUsed(cdict_cParams->strategy, cdict->useRowMatchFinder)) {
23652388
size_t const tagTableSize = hSize;
23662389
ZSTD_memcpy(cctx->blockState.matchState.tagTable,
2367-
cdict->matchState.tagTable,
2368-
tagTableSize);
2390+
cdict->matchState.tagTable,
2391+
tagTableSize);
2392+
cctx->blockState.matchState.hashSalt = cdict->matchState.hashSalt;
23692393
}
23702394
}
23712395

lib/compress/zstd_compress_internal.h

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,8 @@ struct ZSTD_matchState_t {
228228
U32 rowHashLog; /* For row-based matchfinder: Hashlog based on nb of rows in the hashTable.*/
229229
BYTE* tagTable; /* For row-based matchFinder: A row-based table containing the hashes and head index. */
230230
U32 hashCache[ZSTD_ROW_HASH_CACHE_SIZE]; /* For row-based matchFinder: a cache of hashes to improve speed */
231+
U64 hashSalt; /* For row-based matchFinder: salts the hash for re-use of tag table */
232+
U32 hashSaltEntropy; /* For row-based matchFinder: collects entropy for salt generation */
231233

232234
U32* hashTable;
233235
U32* hashTable3;
@@ -787,28 +789,35 @@ ZSTD_count_2segments(const BYTE* ip, const BYTE* match,
787789
* Hashes
788790
***************************************/
789791
static const U32 prime3bytes = 506832829U;
790-
static U32 ZSTD_hash3(U32 u, U32 h) { assert(h <= 32); return ((u << (32-24)) * prime3bytes) >> (32-h) ; }
791-
MEM_STATIC size_t ZSTD_hash3Ptr(const void* ptr, U32 h) { return ZSTD_hash3(MEM_readLE32(ptr), h); } /* only in zstd_opt.h */
792+
static U32 ZSTD_hash3(U32 u, U32 h, U32 s) { assert(h <= 32); return (((u << (32-24)) * prime3bytes) ^ s) >> (32-h) ; }
793+
MEM_STATIC size_t ZSTD_hash3Ptr(const void* ptr, U32 h) { return ZSTD_hash3(MEM_readLE32(ptr), h, 0); } /* only in zstd_opt.h */
794+
MEM_STATIC size_t ZSTD_hash3PtrS(const void* ptr, U32 h, U32 s) { return ZSTD_hash3(MEM_readLE32(ptr), h, s); }
792795

793796
static const U32 prime4bytes = 2654435761U;
794-
static U32 ZSTD_hash4(U32 u, U32 h) { assert(h <= 32); return (u * prime4bytes) >> (32-h) ; }
795-
static size_t ZSTD_hash4Ptr(const void* ptr, U32 h) { return ZSTD_hash4(MEM_readLE32(ptr), h); }
797+
static U32 ZSTD_hash4(U32 u, U32 h, U32 s) { assert(h <= 32); return ((u * prime4bytes) ^ s) >> (32-h) ; }
798+
static size_t ZSTD_hash4Ptr(const void* ptr, U32 h) { return ZSTD_hash4(MEM_readLE32(ptr), h, 0); }
799+
static size_t ZSTD_hash4PtrS(const void* ptr, U32 h, U32 s) { return ZSTD_hash4(MEM_readLE32(ptr), h, s); }
796800

797801
static const U64 prime5bytes = 889523592379ULL;
798-
static size_t ZSTD_hash5(U64 u, U32 h) { assert(h <= 64); return (size_t)(((u << (64-40)) * prime5bytes) >> (64-h)) ; }
799-
static size_t ZSTD_hash5Ptr(const void* p, U32 h) { return ZSTD_hash5(MEM_readLE64(p), h); }
802+
static size_t ZSTD_hash5(U64 u, U32 h, U64 s) { assert(h <= 64); return (size_t)((((u << (64-40)) * prime5bytes) ^ s) >> (64-h)) ; }
803+
static size_t ZSTD_hash5Ptr(const void* p, U32 h) { return ZSTD_hash5(MEM_readLE64(p), h, 0); }
804+
static size_t ZSTD_hash5PtrS(const void* p, U32 h, U64 s) { return ZSTD_hash5(MEM_readLE64(p), h, s); }
800805

801806
static const U64 prime6bytes = 227718039650203ULL;
802-
static size_t ZSTD_hash6(U64 u, U32 h) { assert(h <= 64); return (size_t)(((u << (64-48)) * prime6bytes) >> (64-h)) ; }
803-
static size_t ZSTD_hash6Ptr(const void* p, U32 h) { return ZSTD_hash6(MEM_readLE64(p), h); }
807+
static size_t ZSTD_hash6(U64 u, U32 h, U64 s) { assert(h <= 64); return (size_t)((((u << (64-48)) * prime6bytes) ^ s) >> (64-h)) ; }
808+
static size_t ZSTD_hash6Ptr(const void* p, U32 h) { return ZSTD_hash6(MEM_readLE64(p), h, 0); }
809+
static size_t ZSTD_hash6PtrS(const void* p, U32 h, U64 s) { return ZSTD_hash6(MEM_readLE64(p), h, s); }
804810

805811
static const U64 prime7bytes = 58295818150454627ULL;
806-
static size_t ZSTD_hash7(U64 u, U32 h) { assert(h <= 64); return (size_t)(((u << (64-56)) * prime7bytes) >> (64-h)) ; }
807-
static size_t ZSTD_hash7Ptr(const void* p, U32 h) { return ZSTD_hash7(MEM_readLE64(p), h); }
812+
static size_t ZSTD_hash7(U64 u, U32 h, U64 s) { assert(h <= 64); return (size_t)((((u << (64-56)) * prime7bytes) ^ s) >> (64-h)) ; }
813+
static size_t ZSTD_hash7Ptr(const void* p, U32 h) { return ZSTD_hash7(MEM_readLE64(p), h, 0); }
814+
static size_t ZSTD_hash7PtrS(const void* p, U32 h, U64 s) { return ZSTD_hash7(MEM_readLE64(p), h, s); }
808815

809816
static const U64 prime8bytes = 0xCF1BBCDCB7A56463ULL;
810-
static size_t ZSTD_hash8(U64 u, U32 h) { assert(h <= 64); return (size_t)(((u) * prime8bytes) >> (64-h)) ; }
811-
static size_t ZSTD_hash8Ptr(const void* p, U32 h) { return ZSTD_hash8(MEM_readLE64(p), h); }
817+
static size_t ZSTD_hash8(U64 u, U32 h, U64 s) { assert(h <= 64); return (size_t)((((u) * prime8bytes) ^ s) >> (64-h)) ; }
818+
static size_t ZSTD_hash8Ptr(const void* p, U32 h) { return ZSTD_hash8(MEM_readLE64(p), h, 0); }
819+
static size_t ZSTD_hash8PtrS(const void* p, U32 h, U64 s) { return ZSTD_hash8(MEM_readLE64(p), h, s); }
820+
812821

813822
MEM_STATIC FORCE_INLINE_ATTR
814823
size_t ZSTD_hashPtr(const void* p, U32 hBits, U32 mls)
@@ -828,6 +837,24 @@ size_t ZSTD_hashPtr(const void* p, U32 hBits, U32 mls)
828837
}
829838
}
830839

840+
MEM_STATIC FORCE_INLINE_ATTR
841+
size_t ZSTD_hashPtrSalted(const void* p, U32 hBits, U32 mls, const U64 hashSalt) {
842+
/* Although some of these hashes do support hBits up to 64, some do not.
843+
* To be on the safe side, always avoid hBits > 32. */
844+
assert(hBits <= 32);
845+
846+
switch(mls)
847+
{
848+
default:
849+
case 4: return ZSTD_hash4PtrS(p, hBits, (U32)hashSalt);
850+
case 5: return ZSTD_hash5PtrS(p, hBits, hashSalt);
851+
case 6: return ZSTD_hash6PtrS(p, hBits, hashSalt);
852+
case 7: return ZSTD_hash7PtrS(p, hBits, hashSalt);
853+
case 8: return ZSTD_hash8PtrS(p, hBits, hashSalt);
854+
}
855+
}
856+
857+
831858
/** ZSTD_ipow() :
832859
* Return base^exponent.
833860
*/

lib/compress/zstd_lazy.c

Lines changed: 12 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -773,31 +773,6 @@ MEM_STATIC U32 ZSTD_VecMask_next(ZSTD_VecMask val) {
773773
return ZSTD_countTrailingZeros64(val);
774774
}
775775

776-
/* ZSTD_rotateRight_*():
777-
* Rotates a bitfield to the right by "count" bits.
778-
* https://en.wikipedia.org/w/index.php?title=Circular_shift&oldid=991635599#Implementing_circular_shifts
779-
*/
780-
FORCE_INLINE_TEMPLATE
781-
U64 ZSTD_rotateRight_U64(U64 const value, U32 count) {
782-
assert(count < 64);
783-
count &= 0x3F; /* for fickle pattern recognition */
784-
return (value >> count) | (U64)(value << ((0U - count) & 0x3F));
785-
}
786-
787-
FORCE_INLINE_TEMPLATE
788-
U32 ZSTD_rotateRight_U32(U32 const value, U32 count) {
789-
assert(count < 32);
790-
count &= 0x1F; /* for fickle pattern recognition */
791-
return (value >> count) | (U32)(value << ((0U - count) & 0x1F));
792-
}
793-
794-
FORCE_INLINE_TEMPLATE
795-
U16 ZSTD_rotateRight_U16(U16 const value, U32 count) {
796-
assert(count < 16);
797-
count &= 0x0F; /* for fickle pattern recognition */
798-
return (value >> count) | (U16)(value << ((0U - count) & 0x0F));
799-
}
800-
801776
/* ZSTD_row_nextIndex():
802777
* Returns the next index to insert at within a tagTable row, and updates the "head"
803778
* value to reflect the update. Essentially cycles backwards from [1, {entries per row})
@@ -850,7 +825,7 @@ FORCE_INLINE_TEMPLATE void ZSTD_row_fillHashCache(ZSTD_matchState_t* ms, const B
850825
U32 const lim = idx + MIN(ZSTD_ROW_HASH_CACHE_SIZE, maxElemsToPrefetch);
851826

852827
for (; idx < lim; ++idx) {
853-
U32 const hash = (U32)ZSTD_hashPtr(base + idx, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls);
828+
U32 const hash = (U32)ZSTD_hashPtrSalted(base + idx, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls, ms->hashSalt);
854829
U32 const row = (hash >> ZSTD_ROW_HASH_TAG_BITS) << rowLog;
855830
ZSTD_row_prefetch(hashTable, tagTable, row, rowLog);
856831
ms->hashCache[idx & ZSTD_ROW_HASH_CACHE_MASK] = hash;
@@ -868,9 +843,10 @@ FORCE_INLINE_TEMPLATE void ZSTD_row_fillHashCache(ZSTD_matchState_t* ms, const B
868843
FORCE_INLINE_TEMPLATE U32 ZSTD_row_nextCachedHash(U32* cache, U32 const* hashTable,
869844
BYTE const* tagTable, BYTE const* base,
870845
U32 idx, U32 const hashLog,
871-
U32 const rowLog, U32 const mls)
846+
U32 const rowLog, U32 const mls,
847+
U64 const hashSalt)
872848
{
873-
U32 const newHash = (U32)ZSTD_hashPtr(base+idx+ZSTD_ROW_HASH_CACHE_SIZE, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls);
849+
U32 const newHash = (U32)ZSTD_hashPtrSalted(base+idx+ZSTD_ROW_HASH_CACHE_SIZE, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls, hashSalt);
874850
U32 const row = (newHash >> ZSTD_ROW_HASH_TAG_BITS) << rowLog;
875851
ZSTD_row_prefetch(hashTable, tagTable, row, rowLog);
876852
{ U32 const hash = cache[idx & ZSTD_ROW_HASH_CACHE_MASK];
@@ -890,21 +866,24 @@ FORCE_INLINE_TEMPLATE void ZSTD_row_update_internalImpl(ZSTD_matchState_t* ms,
890866
U32* const hashTable = ms->hashTable;
891867
BYTE* const tagTable = ms->tagTable;
892868
U32 const hashLog = ms->rowHashLog;
869+
U32 hashSaltEntropyCollected = 0;
893870
const BYTE* const base = ms->window.base;
894871

895872
DEBUGLOG(6, "ZSTD_row_update_internalImpl(): updateStartIdx=%u, updateEndIdx=%u", updateStartIdx, updateEndIdx);
896873
for (; updateStartIdx < updateEndIdx; ++updateStartIdx) {
897-
U32 const hash = useCache ? ZSTD_row_nextCachedHash(ms->hashCache, hashTable, tagTable, base, updateStartIdx, hashLog, rowLog, mls)
898-
: (U32)ZSTD_hashPtr(base + updateStartIdx, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls);
874+
U32 const hash = useCache ? ZSTD_row_nextCachedHash(ms->hashCache, hashTable, tagTable, base, updateStartIdx, hashLog, rowLog, mls, ms->hashSalt)
875+
: (U32)ZSTD_hashPtrSalted(base + updateStartIdx, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls, ms->hashSalt);
899876
U32 const relRow = (hash >> ZSTD_ROW_HASH_TAG_BITS) << rowLog;
900877
U32* const row = hashTable + relRow;
901878
BYTE* tagRow = tagTable + relRow;
902879
U32 const pos = ZSTD_row_nextIndex(tagRow, rowMask);
903880

904-
assert(hash == ZSTD_hashPtr(base + updateStartIdx, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls));
881+
assert(hash == ZSTD_hashPtrSalted(base + updateStartIdx, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls, ms->hashSalt));
905882
tagRow[pos] = hash & ZSTD_ROW_HASH_TAG_MASK;
906883
row[pos] = updateStartIdx;
884+
hashSaltEntropyCollected = hash;
907885
}
886+
ms->hashSaltEntropy += hashSaltEntropyCollected; /* collect salt entropy */
908887
}
909888

910889
/* ZSTD_row_update_internal():
@@ -1162,6 +1141,7 @@ size_t ZSTD_RowFindBestMatch(
11621141
const U32 rowMask = rowEntries - 1;
11631142
const U32 cappedSearchLog = MIN(cParams->searchLog, rowLog); /* nb of searches is capped at nb entries per row */
11641143
const U32 groupWidth = ZSTD_row_matchMaskGroupWidth(rowEntries);
1144+
const U64 hashSalt = ms->hashSalt;
11651145
U32 nbAttempts = 1U << cappedSearchLog;
11661146
size_t ml=4-1;
11671147

@@ -1199,7 +1179,7 @@ size_t ZSTD_RowFindBestMatch(
11991179
/* Update the hashTable and tagTable up to (but not including) ip */
12001180
ZSTD_row_update_internal(ms, ip, mls, rowLog, rowMask, 1 /* useCache */);
12011181
{ /* Get the hash for ip, compute the appropriate row */
1202-
U32 const hash = ZSTD_row_nextCachedHash(hashCache, hashTable, tagTable, base, curr, hashLog, rowLog, mls);
1182+
U32 const hash = ZSTD_row_nextCachedHash(hashCache, hashTable, tagTable, base, curr, hashLog, rowLog, mls, hashSalt);
12031183
U32 const relRow = (hash >> ZSTD_ROW_HASH_TAG_BITS) << rowLog;
12041184
U32 const tag = hash & ZSTD_ROW_HASH_TAG_MASK;
12051185
U32* const row = hashTable + relRow;

0 commit comments

Comments
 (0)