Skip to content

Commit 0273206

Browse files
committed
Introduce salt into row hash (#3528)
This 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 c4fd893 commit 0273206

File tree

3 files changed

+51
-21
lines changed

3 files changed

+51
-21
lines changed

lib/compress/zstd_compress.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1936,6 +1936,7 @@ ZSTD_reset_matchState(ZSTD_matchState_t* ms,
19361936
/* Row match finder needs an additional table of hashes ("tags") */
19371937
size_t const tagTableSize = hSize * sizeof(U16);
19381938
ms->tagTable = (U16 *) ZSTD_cwksp_reserve_aligned_init_once(ws, tagTableSize);
1939+
ms->hashSalt = ms->hashSalt * 6364136223846793005 + 1; /* based on MUSL rand */
19391940
{ /* Switch to 32-entry rows if searchLog is 5 (or more) */
19401941
U32 const rowLog = BOUNDED(4, cParams->searchLog, 6);
19411942
assert(cParams->hashLog >= rowLog);
@@ -2341,8 +2342,9 @@ static size_t ZSTD_resetCCtx_byCopyingCDict(ZSTD_CCtx* cctx,
23412342
if (ZSTD_rowMatchFinderUsed(cdict_cParams->strategy, cdict->useRowMatchFinder)) {
23422343
size_t const tagTableSize = hSize*sizeof(U16);
23432344
ZSTD_memcpy(cctx->blockState.matchState.tagTable,
2344-
cdict->matchState.tagTable,
2345-
tagTableSize);
2345+
cdict->matchState.tagTable,
2346+
tagTableSize);
2347+
cctx->blockState.matchState.hashSalt = cdict->matchState.hashSalt;
23462348
}
23472349
}
23482350

lib/compress/zstd_compress_internal.h

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ struct ZSTD_matchState_t {
228228
U32 rowHashLog; /* For row-based matchfinder: Hashlog based on nb of rows in the hashTable.*/
229229
U16* 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;
231232

232233
U32* hashTable;
233234
U32* hashTable3;
@@ -787,28 +788,35 @@ ZSTD_count_2segments(const BYTE* ip, const BYTE* match,
787788
* Hashes
788789
***************************************/
789790
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 */
791+
static U32 ZSTD_hash3(U32 u, U32 h, U32 s) { assert(h <= 32); return (((u << (32-24)) * prime3bytes) ^ s) >> (32-h) ; }
792+
MEM_STATIC size_t ZSTD_hash3Ptr(const void* ptr, U32 h) { return ZSTD_hash3(MEM_readLE32(ptr), h, 0); } /* only in zstd_opt.h */
793+
MEM_STATIC size_t ZSTD_hash3PtrS(const void* ptr, U32 h, U32 s) { return ZSTD_hash3(MEM_readLE32(ptr), h, s); }
792794

793795
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); }
796+
static U32 ZSTD_hash4(U32 u, U32 h, U32 s) { assert(h <= 32); return ((u * prime4bytes) ^ s) >> (32-h) ; }
797+
static size_t ZSTD_hash4Ptr(const void* ptr, U32 h) { return ZSTD_hash4(MEM_readLE32(ptr), h, 0); }
798+
static size_t ZSTD_hash4PtrS(const void* ptr, U32 h, U32 s) { return ZSTD_hash4(MEM_readLE32(ptr), h, s); }
796799

797800
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); }
801+
static size_t ZSTD_hash5(U64 u, U32 h, U64 s) { assert(h <= 64); return (size_t)((((u << (64-40)) * prime5bytes) ^ s) >> (64-h)) ; }
802+
static size_t ZSTD_hash5Ptr(const void* p, U32 h) { return ZSTD_hash5(MEM_readLE64(p), h, 0); }
803+
static size_t ZSTD_hash5PtrS(const void* p, U32 h, U64 s) { return ZSTD_hash5(MEM_readLE64(p), h, s); }
800804

801805
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); }
806+
static size_t ZSTD_hash6(U64 u, U32 h, U64 s) { assert(h <= 64); return (size_t)((((u << (64-48)) * prime6bytes) ^ s) >> (64-h)) ; }
807+
static size_t ZSTD_hash6Ptr(const void* p, U32 h) { return ZSTD_hash6(MEM_readLE64(p), h, 0); }
808+
static size_t ZSTD_hash6PtrS(const void* p, U32 h, U64 s) { return ZSTD_hash6(MEM_readLE64(p), h, s); }
804809

805810
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); }
811+
static size_t ZSTD_hash7(U64 u, U32 h, U64 s) { assert(h <= 64); return (size_t)((((u << (64-56)) * prime7bytes) ^ s) >> (64-h)) ; }
812+
static size_t ZSTD_hash7Ptr(const void* p, U32 h) { return ZSTD_hash7(MEM_readLE64(p), h, 0); }
813+
static size_t ZSTD_hash7PtrS(const void* p, U32 h, U64 s) { return ZSTD_hash7(MEM_readLE64(p), h, s); }
808814

809815
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); }
816+
static size_t ZSTD_hash8(U64 u, U32 h, U64 s) { assert(h <= 64); return (size_t)((((u) * prime8bytes) ^ s) >> (64-h)) ; }
817+
static size_t ZSTD_hash8Ptr(const void* p, U32 h) { return ZSTD_hash8(MEM_readLE64(p), h, 0); }
818+
static size_t ZSTD_hash8PtrS(const void* p, U32 h, U64 s) { return ZSTD_hash8(MEM_readLE64(p), h, s); }
819+
812820

813821
MEM_STATIC FORCE_INLINE_ATTR
814822
size_t ZSTD_hashPtr(const void* p, U32 hBits, U32 mls)
@@ -828,6 +836,24 @@ size_t ZSTD_hashPtr(const void* p, U32 hBits, U32 mls)
828836
}
829837
}
830838

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

lib/compress/zstd_lazy.c

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -850,7 +850,7 @@ FORCE_INLINE_TEMPLATE void ZSTD_row_fillHashCache(ZSTD_matchState_t* ms, const B
850850
U32 const lim = idx + MIN(ZSTD_ROW_HASH_CACHE_SIZE, maxElemsToPrefetch);
851851

852852
for (; idx < lim; ++idx) {
853-
U32 const hash = (U32)ZSTD_hashPtr(base + idx, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls);
853+
U32 const hash = (U32)ZSTD_hashPtrSalted(base + idx, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls, ms->hashSalt);
854854
U32 const row = (hash >> ZSTD_ROW_HASH_TAG_BITS) << rowLog;
855855
ZSTD_row_prefetch(hashTable, tagTable, row, rowLog);
856856
ms->hashCache[idx & ZSTD_ROW_HASH_CACHE_MASK] = hash;
@@ -868,9 +868,10 @@ FORCE_INLINE_TEMPLATE void ZSTD_row_fillHashCache(ZSTD_matchState_t* ms, const B
868868
FORCE_INLINE_TEMPLATE U32 ZSTD_row_nextCachedHash(U32* cache, U32 const* hashTable,
869869
U16 const* tagTable, BYTE const* base,
870870
U32 idx, U32 const hashLog,
871-
U32 const rowLog, U32 const mls)
871+
U32 const rowLog, U32 const mls,
872+
U64 const hashSalt)
872873
{
873-
U32 const newHash = (U32)ZSTD_hashPtr(base+idx+ZSTD_ROW_HASH_CACHE_SIZE, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls);
874+
U32 const newHash = (U32)ZSTD_hashPtrSalted(base+idx+ZSTD_ROW_HASH_CACHE_SIZE, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls, hashSalt);
874875
U32 const row = (newHash >> ZSTD_ROW_HASH_TAG_BITS) << rowLog;
875876
ZSTD_row_prefetch(hashTable, tagTable, row, rowLog);
876877
{ U32 const hash = cache[idx & ZSTD_ROW_HASH_CACHE_MASK];
@@ -894,15 +895,15 @@ FORCE_INLINE_TEMPLATE void ZSTD_row_update_internalImpl(ZSTD_matchState_t* ms,
894895

895896
DEBUGLOG(6, "ZSTD_row_update_internalImpl(): updateStartIdx=%u, updateEndIdx=%u", updateStartIdx, updateEndIdx);
896897
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);
898+
U32 const hash = useCache ? ZSTD_row_nextCachedHash(ms->hashCache, hashTable, tagTable, base, updateStartIdx, hashLog, rowLog, mls, ms->hashSalt)
899+
: (U32)ZSTD_hashPtrSalted(base + updateStartIdx, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls, ms->hashSalt);
899900
U32 const relRow = (hash >> ZSTD_ROW_HASH_TAG_BITS) << rowLog;
900901
U32* const row = hashTable + relRow;
901902
BYTE* tagRow = (BYTE*)(tagTable + relRow); /* Though tagTable is laid out as a table of U16, each tag is only 1 byte.
902903
Explicit cast allows us to get exact desired position within each row */
903904
U32 const pos = ZSTD_row_nextIndex(tagRow, rowMask);
904905

905-
assert(hash == ZSTD_hashPtr(base + updateStartIdx, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls));
906+
assert(hash == ZSTD_hashPtrSalted(base + updateStartIdx, hashLog + ZSTD_ROW_HASH_TAG_BITS, mls, ms->hashSalt));
906907
((BYTE*)tagRow)[pos + ZSTD_ROW_HASH_TAG_OFFSET] = hash & ZSTD_ROW_HASH_TAG_MASK;
907908
row[pos] = updateStartIdx;
908909
}
@@ -1163,6 +1164,7 @@ size_t ZSTD_RowFindBestMatch(
11631164
const U32 rowMask = rowEntries - 1;
11641165
const U32 cappedSearchLog = MIN(cParams->searchLog, rowLog); /* nb of searches is capped at nb entries per row */
11651166
const U32 groupWidth = ZSTD_row_matchMaskGroupWidth(rowEntries);
1167+
const U64 hashSalt = ms->hashSalt;
11661168
U32 nbAttempts = 1U << cappedSearchLog;
11671169
size_t ml=4-1;
11681170

@@ -1200,7 +1202,7 @@ size_t ZSTD_RowFindBestMatch(
12001202
/* Update the hashTable and tagTable up to (but not including) ip */
12011203
ZSTD_row_update_internal(ms, ip, mls, rowLog, rowMask, 1 /* useCache */);
12021204
{ /* Get the hash for ip, compute the appropriate row */
1203-
U32 const hash = ZSTD_row_nextCachedHash(hashCache, hashTable, tagTable, base, curr, hashLog, rowLog, mls);
1205+
U32 const hash = ZSTD_row_nextCachedHash(hashCache, hashTable, tagTable, base, curr, hashLog, rowLog, mls, hashSalt);
12041206
U32 const relRow = (hash >> ZSTD_ROW_HASH_TAG_BITS) << rowLog;
12051207
U32 const tag = hash & ZSTD_ROW_HASH_TAG_MASK;
12061208
U32* const row = hashTable + relRow;

0 commit comments

Comments
 (0)