From 6a546efb8c90adf7865801e313da433ce8406ce1 Mon Sep 17 00:00:00 2001 From: Stella Lau Date: Fri, 28 Jul 2017 15:51:33 -0700 Subject: [PATCH 01/15] Add long distance matcher Move last literals section to ZSTD_block_internal --- lib/common/zstd_internal.h | 14 + lib/compress/zstd_compress.c | 934 +++++++++++++++++++++++++++++++---- lib/compress/zstd_opt.h | 24 +- lib/zstd.h | 4 +- programs/bench.c | 6 +- programs/bench.h | 1 + programs/fileio.c | 6 + programs/fileio.h | 1 + programs/zstdcli.c | 7 +- tests/fuzzer.c | 4 +- tests/playTests.sh | 22 + tests/zstreamtest.c | 2 + 12 files changed, 901 insertions(+), 124 deletions(-) diff --git a/lib/common/zstd_internal.h b/lib/common/zstd_internal.h index 19c0a626..49b21c2d 100644 --- a/lib/common/zstd_internal.h +++ b/lib/common/zstd_internal.h @@ -274,6 +274,20 @@ typedef struct { const BYTE* cachedLiterals; } optState_t; +typedef struct { + U32 offset; + U32 checksum; +} ldmEntry_t; + +typedef struct { + ldmEntry_t* hashTable; + BYTE* bucketOffsets; + U32 ldmEnable; /* 1 if enable long distance matching */ + U32 hashLog; /* log size of hashTable */ + U32 bucketLog; /* log number of buckets, at most 4 */ + U32 hashEveryLog; +} ldmState_t; + typedef struct { U32 hufCTable[HUF_CTABLE_SIZE_U32(255)]; FSE_CTable offcodeCTable[FSE_CTABLE_SIZE_U32(OffFSELog, MaxOff)]; diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 5adeb480..331b2120 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -36,6 +36,13 @@ static const U32 g_searchStrength = 8; /* control skip over incompressible dat #define HASH_READ_SIZE 8 typedef enum { ZSTDcs_created=0, ZSTDcs_init, ZSTDcs_ongoing, ZSTDcs_ending } ZSTD_compressionStage_e; +#define LDM_BUCKET_SIZE_LOG 3 +#define LDM_BUCKET_SIZE_LOG_MAX 4 +#define LDM_MIN_MATCH_LENGTH 64 +#define LDM_WINDOW_LOG 27 +#define LDM_HASH_LOG 20 +#define LDM_HASH_CHAR_OFFSET 10 + /*-************************************* * Helper functions @@ -46,7 +53,6 @@ size_t ZSTD_compressBound(size_t srcSize) { return srcSize + (srcSize >> 8) + margin; } - /*-************************************* * Sequence storage ***************************************/ @@ -101,6 +107,7 @@ struct ZSTD_CCtx_s { seqStore_t seqStore; /* sequences storage ptrs */ optState_t optState; + ldmState_t ldmState; /* long distance matching state */ U32* hashTable; U32* hashTable3; U32* chainTable; @@ -354,6 +361,16 @@ size_t ZSTD_CCtx_setParameter(ZSTD_CCtx* cctx, ZSTD_cParameter param, unsigned v DEBUGLOG(5, " setting overlap with nbThreads == %u", cctx->requestedParams.nbThreads); return ZSTD_CCtxParam_setParameter(&cctx->requestedParams, param, value); + case ZSTD_p_longDistanceMatching: + /* TODO */ + if (cctx->cdict) return ERROR(stage_wrong); + cctx->ldmState.ldmEnable = value>0; + if (value != 0) { + ZSTD_cLevelToCParams(cctx); + cctx->requestedParams.cParams.windowLog = LDM_WINDOW_LOG; + } + return 0; + default: return ERROR(parameter_unsupported); } } @@ -453,6 +470,10 @@ size_t ZSTD_CCtxParam_setParameter( if (params->nbThreads <= 1) return ERROR(parameter_unsupported); return ZSTDMT_CCtxParam_setMTCtxParameter(params, ZSTDMT_p_overlapSectionLog, value); + case ZSTD_p_longDistanceMatching : + /* TODO */ + return ERROR(parameter_unsupported); + default: return ERROR(parameter_unsupported); } } @@ -677,7 +698,11 @@ size_t ZSTD_estimateCCtxSize_advanced_usingCCtxParams(const ZSTD_CCtx_params* pa ((MaxML+1) + (MaxLL+1) + (MaxOff+1) + (1<appliedParams)) { + /* TODO: For now, reset if long distance matching is enabled */ + if (ZSTD_equivalentParams(params, zc->appliedParams) && + !zc->ldmState.ldmEnable) { DEBUGLOG(5, "ZSTD_equivalentParams()==1"); zc->entropy->hufCTable_repeatMode = HUF_repeat_none; zc->entropy->offcode_repeatMode = FSE_repeat_none; @@ -787,6 +814,15 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, return ZSTD_continueCCtx(zc, params, pledgedSrcSize); } } + { + zc->ldmState.hashLog = LDM_HASH_LOG; + zc->ldmState.bucketLog = + MIN(LDM_BUCKET_SIZE_LOG, LDM_BUCKET_SIZE_LOG_MAX); + zc->ldmState.hashEveryLog = + params.cParams.windowLog < zc->ldmState.hashLog ? + 0 : params.cParams.windowLog - zc->ldmState.hashLog; + } + { size_t const blockSize = MIN(ZSTD_BLOCKSIZE_MAX, (size_t)1 << params.cParams.windowLog); U32 const divider = (params.cParams.searchLength==3) ? 3 : 4; size_t const maxNbSeq = blockSize / divider; @@ -802,6 +838,14 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, size_t const buffInSize = (zbuff==ZSTDb_buffered) ? ((size_t)1 << params.cParams.windowLog) + blockSize : 0; void* ptr; + size_t const ldmHSize = ((size_t)1) << zc->ldmState.hashLog; + size_t const ldmBucketSize = + ((size_t)1) << (zc->ldmState.hashLog - zc->ldmState.bucketLog); + size_t const ldmPotentialSpace = + ldmBucketSize + (ldmHSize * (sizeof(ldmEntry_t))); + size_t const ldmSpace = zc->ldmState.ldmEnable ? + ldmPotentialSpace : 0; + /* Check if workSpace is large enough, alloc a new one if needed */ { size_t const entropySpace = sizeof(ZSTD_entropyCTables_t); size_t const optPotentialSpace = ((MaxML+1) + (MaxLL+1) + (MaxOff+1) + (1<workSpaceSize < neededSpace) { /* too small : resize /*/ + if (zc->workSpaceSize < neededSpace) { /* too small : resize */ DEBUGLOG(5, "Need to update workSpaceSize from %uK to %uK \n", (unsigned)zc->workSpaceSize>>10, (unsigned)neededSpace>>10); @@ -878,6 +922,16 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, ptr = zc->optState.priceTable + ZSTD_OPT_NUM+1; } + /* ldm space */ + if (zc->ldmState.ldmEnable) { + if (crp!=ZSTDcrp_noMemset) memset(ptr, 0, ldmSpace); + assert(((size_t)ptr & 3) == 0); /* ensure ptr is properly aligned */ + zc->ldmState.hashTable = (ldmEntry_t*)ptr; + ptr = zc->ldmState.hashTable + ldmHSize; + zc->ldmState.bucketOffsets = (BYTE*)ptr; + ptr = zc->ldmState.bucketOffsets + ldmBucketSize; + } + /* table Space */ if (crp!=ZSTDcrp_noMemset) memset(ptr, 0, tableSpace); /* reset tables only */ assert(((size_t)ptr & 3) == 0); /* ensure ptr is properly aligned */ @@ -990,6 +1044,18 @@ static void ZSTD_reduceTable (U32* const table, U32 const size, U32 const reduce } } +/*! ZSTD_ldm_reduceTable() : + * reduce table indexes by `reducerValue` */ +static void ZSTD_ldm_reduceTable(ldmEntry_t* const table, U32 const size, + U32 const reducerValue) +{ + U32 u; + for (u = 0; u < size; u++) { + if (table[u].offset < reducerValue) table[u].offset = 0; + else table[u].offset -= reducerValue; + } +} + /*! ZSTD_reduceIndex() : * rescale all indexes to avoid future overflow (indexes are U32) */ static void ZSTD_reduceIndex (ZSTD_CCtx* zc, const U32 reducerValue) @@ -1002,6 +1068,12 @@ static void ZSTD_reduceIndex (ZSTD_CCtx* zc, const U32 reducerValue) { U32 const h3Size = (zc->hashLog3) ? 1 << zc->hashLog3 : 0; ZSTD_reduceTable(zc->hashTable3, h3Size, reducerValue); } + + { if (zc->ldmState.ldmEnable) { + U32 const ldmHSize = 1 << LDM_HASH_LOG; + ZSTD_ldm_reduceTable(zc->ldmState.hashTable, ldmHSize, reducerValue); + } + } } @@ -1611,7 +1683,6 @@ static size_t ZSTD_hashPtr(const void* p, U32 hBits, U32 mls) } } - /*-************************************* * Fast Scan ***************************************/ @@ -1630,11 +1701,10 @@ static void ZSTD_fillHashTable (ZSTD_CCtx* zc, const void* end, const U32 mls) } } - FORCE_INLINE -void ZSTD_compressBlock_fast_generic(ZSTD_CCtx* cctx, - const void* src, size_t srcSize, - const U32 mls) +size_t ZSTD_compressBlock_fast_generic(ZSTD_CCtx* cctx, + const void* src, size_t srcSize, + const U32 mls) { U32* const hashTable = cctx->hashTable; U32 const hBits = cctx->appliedParams.cParams.hashLog; @@ -1681,7 +1751,6 @@ void ZSTD_compressBlock_fast_generic(ZSTD_CCtx* cctx, while (((ip>anchor) & (match>lowest)) && (ip[-1] == match[-1])) { ip--; match--; mLength++; } /* catch up */ offset_2 = offset_1; offset_1 = offset; - ZSTD_storeSeq(seqStorePtr, ip-anchor, anchor, offset + ZSTD_REP_MOVE, mLength-MINMATCH); } @@ -1711,15 +1780,11 @@ void ZSTD_compressBlock_fast_generic(ZSTD_CCtx* cctx, seqStorePtr->repToConfirm[0] = offset_1 ? offset_1 : offsetSaved; seqStorePtr->repToConfirm[1] = offset_2 ? offset_2 : offsetSaved; - /* Last Literals */ - { size_t const lastLLSize = iend - anchor; - memcpy(seqStorePtr->lit, anchor, lastLLSize); - seqStorePtr->lit += lastLLSize; - } + /* Return the last literals size */ + return iend - anchor; } - -static void ZSTD_compressBlock_fast(ZSTD_CCtx* ctx, +static size_t ZSTD_compressBlock_fast(ZSTD_CCtx* ctx, const void* src, size_t srcSize) { const U32 mls = ctx->appliedParams.cParams.searchLength; @@ -1727,18 +1792,19 @@ static void ZSTD_compressBlock_fast(ZSTD_CCtx* ctx, { default: /* includes case 3 */ case 4 : - ZSTD_compressBlock_fast_generic(ctx, src, srcSize, 4); return; + return ZSTD_compressBlock_fast_generic(ctx, src, srcSize, 4); case 5 : - ZSTD_compressBlock_fast_generic(ctx, src, srcSize, 5); return; + return ZSTD_compressBlock_fast_generic(ctx, src, srcSize, 5); case 6 : - ZSTD_compressBlock_fast_generic(ctx, src, srcSize, 6); return; + return ZSTD_compressBlock_fast_generic(ctx, src, srcSize, 6); case 7 : - ZSTD_compressBlock_fast_generic(ctx, src, srcSize, 7); return; + return ZSTD_compressBlock_fast_generic(ctx, src, srcSize, 7); } } -static void ZSTD_compressBlock_fast_extDict_generic(ZSTD_CCtx* ctx, +static size_t ZSTD_compressBlock_fast_extDict_generic( + ZSTD_CCtx* ctx, const void* src, size_t srcSize, const U32 mls) { @@ -1825,15 +1891,11 @@ static void ZSTD_compressBlock_fast_extDict_generic(ZSTD_CCtx* ctx, /* save reps for next block */ seqStorePtr->repToConfirm[0] = offset_1; seqStorePtr->repToConfirm[1] = offset_2; - /* Last Literals */ - { size_t const lastLLSize = iend - anchor; - memcpy(seqStorePtr->lit, anchor, lastLLSize); - seqStorePtr->lit += lastLLSize; - } + /* Return the last literals size */ + return iend - anchor; } - -static void ZSTD_compressBlock_fast_extDict(ZSTD_CCtx* ctx, +static size_t ZSTD_compressBlock_fast_extDict(ZSTD_CCtx* ctx, const void* src, size_t srcSize) { U32 const mls = ctx->appliedParams.cParams.searchLength; @@ -1841,13 +1903,13 @@ static void ZSTD_compressBlock_fast_extDict(ZSTD_CCtx* ctx, { default: /* includes case 3 */ case 4 : - ZSTD_compressBlock_fast_extDict_generic(ctx, src, srcSize, 4); return; + return ZSTD_compressBlock_fast_extDict_generic(ctx, src, srcSize, 4); case 5 : - ZSTD_compressBlock_fast_extDict_generic(ctx, src, srcSize, 5); return; + return ZSTD_compressBlock_fast_extDict_generic(ctx, src, srcSize, 5); case 6 : - ZSTD_compressBlock_fast_extDict_generic(ctx, src, srcSize, 6); return; + return ZSTD_compressBlock_fast_extDict_generic(ctx, src, srcSize, 6); case 7 : - ZSTD_compressBlock_fast_extDict_generic(ctx, src, srcSize, 7); return; + return ZSTD_compressBlock_fast_extDict_generic(ctx, src, srcSize, 7); } } @@ -1875,7 +1937,7 @@ static void ZSTD_fillDoubleHashTable (ZSTD_CCtx* cctx, const void* end, const U3 FORCE_INLINE -void ZSTD_compressBlock_doubleFast_generic(ZSTD_CCtx* cctx, +size_t ZSTD_compressBlock_doubleFast_generic(ZSTD_CCtx* cctx, const void* src, size_t srcSize, const U32 mls) { @@ -1921,6 +1983,7 @@ void ZSTD_compressBlock_doubleFast_generic(ZSTD_CCtx* cctx, ip++; ZSTD_storeSeq(seqStorePtr, ip-anchor, anchor, 0, mLength-MINMATCH); } else { + U32 offset; if ( (matchIndexL > lowestIndex) && (MEM_read64(matchLong) == MEM_read64(ip)) ) { mLength = ZSTD_count(ip+8, matchLong+8, iend) + 8; @@ -1982,33 +2045,32 @@ void ZSTD_compressBlock_doubleFast_generic(ZSTD_CCtx* cctx, seqStorePtr->repToConfirm[0] = offset_1 ? offset_1 : offsetSaved; seqStorePtr->repToConfirm[1] = offset_2 ? offset_2 : offsetSaved; - /* Last Literals */ - { size_t const lastLLSize = iend - anchor; - memcpy(seqStorePtr->lit, anchor, lastLLSize); - seqStorePtr->lit += lastLLSize; - } + /* Return the last literals size */ + return iend - anchor; } -static void ZSTD_compressBlock_doubleFast(ZSTD_CCtx* ctx, const void* src, size_t srcSize) +static size_t ZSTD_compressBlock_doubleFast(ZSTD_CCtx* ctx, + const void* src, size_t srcSize) { const U32 mls = ctx->appliedParams.cParams.searchLength; switch(mls) { default: /* includes case 3 */ case 4 : - ZSTD_compressBlock_doubleFast_generic(ctx, src, srcSize, 4); return; + return ZSTD_compressBlock_doubleFast_generic(ctx, src, srcSize, 4); case 5 : - ZSTD_compressBlock_doubleFast_generic(ctx, src, srcSize, 5); return; + return ZSTD_compressBlock_doubleFast_generic(ctx, src, srcSize, 5); case 6 : - ZSTD_compressBlock_doubleFast_generic(ctx, src, srcSize, 6); return; + return ZSTD_compressBlock_doubleFast_generic(ctx, src, srcSize, 6); case 7 : - ZSTD_compressBlock_doubleFast_generic(ctx, src, srcSize, 7); return; + return ZSTD_compressBlock_doubleFast_generic(ctx, src, srcSize, 7); } } -static void ZSTD_compressBlock_doubleFast_extDict_generic(ZSTD_CCtx* ctx, +static size_t ZSTD_compressBlock_doubleFast_extDict_generic( + ZSTD_CCtx* ctx, const void* src, size_t srcSize, const U32 mls) { @@ -2131,15 +2193,12 @@ static void ZSTD_compressBlock_doubleFast_extDict_generic(ZSTD_CCtx* ctx, /* save reps for next block */ seqStorePtr->repToConfirm[0] = offset_1; seqStorePtr->repToConfirm[1] = offset_2; - /* Last Literals */ - { size_t const lastLLSize = iend - anchor; - memcpy(seqStorePtr->lit, anchor, lastLLSize); - seqStorePtr->lit += lastLLSize; - } + /* Return the last literals size */ + return iend - anchor; } -static void ZSTD_compressBlock_doubleFast_extDict(ZSTD_CCtx* ctx, +static size_t ZSTD_compressBlock_doubleFast_extDict(ZSTD_CCtx* ctx, const void* src, size_t srcSize) { U32 const mls = ctx->appliedParams.cParams.searchLength; @@ -2147,13 +2206,13 @@ static void ZSTD_compressBlock_doubleFast_extDict(ZSTD_CCtx* ctx, { default: /* includes case 3 */ case 4 : - ZSTD_compressBlock_doubleFast_extDict_generic(ctx, src, srcSize, 4); return; + return ZSTD_compressBlock_doubleFast_extDict_generic(ctx, src, srcSize, 4); case 5 : - ZSTD_compressBlock_doubleFast_extDict_generic(ctx, src, srcSize, 5); return; + return ZSTD_compressBlock_doubleFast_extDict_generic(ctx, src, srcSize, 5); case 6 : - ZSTD_compressBlock_doubleFast_extDict_generic(ctx, src, srcSize, 6); return; + return ZSTD_compressBlock_doubleFast_extDict_generic(ctx, src, srcSize, 6); case 7 : - ZSTD_compressBlock_doubleFast_extDict_generic(ctx, src, srcSize, 7); return; + return ZSTD_compressBlock_doubleFast_extDict_generic(ctx, src, srcSize, 7); } } @@ -2546,9 +2605,9 @@ FORCE_INLINE size_t ZSTD_HcFindBestMatch_extDict_selectMLS ( * Common parser - lazy strategy *********************************/ FORCE_INLINE -void ZSTD_compressBlock_lazy_generic(ZSTD_CCtx* ctx, - const void* src, size_t srcSize, - const U32 searchMethod, const U32 depth) +size_t ZSTD_compressBlock_lazy_generic(ZSTD_CCtx* ctx, + const void* src, size_t srcSize, + const U32 searchMethod, const U32 depth) { seqStore_t* seqStorePtr = &(ctx->seqStore); const BYTE* const istart = (const BYTE*)src; @@ -2678,37 +2737,38 @@ _storeSequence: seqStorePtr->repToConfirm[0] = offset_1 ? offset_1 : savedOffset; seqStorePtr->repToConfirm[1] = offset_2 ? offset_2 : savedOffset; - /* Last Literals */ - { size_t const lastLLSize = iend - anchor; - memcpy(seqStorePtr->lit, anchor, lastLLSize); - seqStorePtr->lit += lastLLSize; - } + /* Return the last literals size */ + return iend - anchor; } -static void ZSTD_compressBlock_btlazy2(ZSTD_CCtx* ctx, const void* src, size_t srcSize) +static size_t ZSTD_compressBlock_btlazy2(ZSTD_CCtx* ctx, const void* src, + size_t srcSize) { - ZSTD_compressBlock_lazy_generic(ctx, src, srcSize, 1, 2); + return ZSTD_compressBlock_lazy_generic(ctx, src, srcSize, 1, 2); } -static void ZSTD_compressBlock_lazy2(ZSTD_CCtx* ctx, const void* src, size_t srcSize) +static size_t ZSTD_compressBlock_lazy2(ZSTD_CCtx* ctx, const void* src, + size_t srcSize) { - ZSTD_compressBlock_lazy_generic(ctx, src, srcSize, 0, 2); + return ZSTD_compressBlock_lazy_generic(ctx, src, srcSize, 0, 2); } -static void ZSTD_compressBlock_lazy(ZSTD_CCtx* ctx, const void* src, size_t srcSize) +static size_t ZSTD_compressBlock_lazy(ZSTD_CCtx* ctx, const void* src, + size_t srcSize) { - ZSTD_compressBlock_lazy_generic(ctx, src, srcSize, 0, 1); + return ZSTD_compressBlock_lazy_generic(ctx, src, srcSize, 0, 1); } -static void ZSTD_compressBlock_greedy(ZSTD_CCtx* ctx, const void* src, size_t srcSize) +static size_t ZSTD_compressBlock_greedy(ZSTD_CCtx* ctx, const void* src, + size_t srcSize) { - ZSTD_compressBlock_lazy_generic(ctx, src, srcSize, 0, 0); + return ZSTD_compressBlock_lazy_generic(ctx, src, srcSize, 0, 0); } FORCE_INLINE -void ZSTD_compressBlock_lazy_extDict_generic(ZSTD_CCtx* ctx, +size_t ZSTD_compressBlock_lazy_extDict_generic(ZSTD_CCtx* ctx, const void* src, size_t srcSize, const U32 searchMethod, const U32 depth) { @@ -2774,7 +2834,7 @@ void ZSTD_compressBlock_lazy_extDict_generic(ZSTD_CCtx* ctx, /* let's try to find a better solution */ if (depth>=1) while (iprepToConfirm[0] = offset_1; seqStorePtr->repToConfirm[1] = offset_2; - /* Last Literals */ - { size_t const lastLLSize = iend - anchor; - memcpy(seqStorePtr->lit, anchor, lastLLSize); - seqStorePtr->lit += lastLLSize; - } + /* Return the last literals size */ + return iend - anchor; } -void ZSTD_compressBlock_greedy_extDict(ZSTD_CCtx* ctx, const void* src, size_t srcSize) +size_t ZSTD_compressBlock_greedy_extDict(ZSTD_CCtx* ctx, const void* src, size_t srcSize) { - ZSTD_compressBlock_lazy_extDict_generic(ctx, src, srcSize, 0, 0); + return ZSTD_compressBlock_lazy_extDict_generic(ctx, src, srcSize, 0, 0); } -static void ZSTD_compressBlock_lazy_extDict(ZSTD_CCtx* ctx, const void* src, size_t srcSize) +static size_t ZSTD_compressBlock_lazy_extDict(ZSTD_CCtx* ctx, const void* src, size_t srcSize) { - ZSTD_compressBlock_lazy_extDict_generic(ctx, src, srcSize, 0, 1); + return ZSTD_compressBlock_lazy_extDict_generic(ctx, src, srcSize, 0, 1); } -static void ZSTD_compressBlock_lazy2_extDict(ZSTD_CCtx* ctx, const void* src, size_t srcSize) +static size_t ZSTD_compressBlock_lazy2_extDict(ZSTD_CCtx* ctx, const void* src, size_t srcSize) { - ZSTD_compressBlock_lazy_extDict_generic(ctx, src, srcSize, 0, 2); + return ZSTD_compressBlock_lazy_extDict_generic(ctx, src, srcSize, 0, 2); } -static void ZSTD_compressBlock_btlazy2_extDict(ZSTD_CCtx* ctx, const void* src, size_t srcSize) +static size_t ZSTD_compressBlock_btlazy2_extDict(ZSTD_CCtx* ctx, const void* src, size_t srcSize) { - ZSTD_compressBlock_lazy_extDict_generic(ctx, src, srcSize, 1, 2); + return ZSTD_compressBlock_lazy_extDict_generic(ctx, src, srcSize, 1, 2); } /* The optimal parser */ #include "zstd_opt.h" -static void ZSTD_compressBlock_btopt(ZSTD_CCtx* ctx, const void* src, size_t srcSize) +static size_t ZSTD_compressBlock_btopt(ZSTD_CCtx* ctx, const void* src, size_t srcSize) { #ifdef ZSTD_OPT_H_91842398743 - ZSTD_compressBlock_opt_generic(ctx, src, srcSize, 0); + return ZSTD_compressBlock_opt_generic(ctx, src, srcSize, 0); #else (void)ctx; (void)src; (void)srcSize; - return; + return 0; #endif } -static void ZSTD_compressBlock_btultra(ZSTD_CCtx* ctx, const void* src, size_t srcSize) +static size_t ZSTD_compressBlock_btultra(ZSTD_CCtx* ctx, const void* src, size_t srcSize) { #ifdef ZSTD_OPT_H_91842398743 - ZSTD_compressBlock_opt_generic(ctx, src, srcSize, 1); + return ZSTD_compressBlock_opt_generic(ctx, src, srcSize, 1); #else (void)ctx; (void)src; (void)srcSize; - return; + return 0; #endif } -static void ZSTD_compressBlock_btopt_extDict(ZSTD_CCtx* ctx, const void* src, size_t srcSize) +static size_t ZSTD_compressBlock_btopt_extDict(ZSTD_CCtx* ctx, const void* src, size_t srcSize) { #ifdef ZSTD_OPT_H_91842398743 - ZSTD_compressBlock_opt_extDict_generic(ctx, src, srcSize, 0); + return ZSTD_compressBlock_opt_extDict_generic(ctx, src, srcSize, 0); #else (void)ctx; (void)src; (void)srcSize; - return; + return 0; #endif } -static void ZSTD_compressBlock_btultra_extDict(ZSTD_CCtx* ctx, const void* src, size_t srcSize) +static size_t ZSTD_compressBlock_btultra_extDict(ZSTD_CCtx* ctx, const void* src, size_t srcSize) { #ifdef ZSTD_OPT_H_91842398743 - ZSTD_compressBlock_opt_extDict_generic(ctx, src, srcSize, 1); + return ZSTD_compressBlock_opt_extDict_generic(ctx, src, srcSize, 1); #else (void)ctx; (void)src; (void)srcSize; - return; + return 0; #endif } - /* ZSTD_selectBlockCompressor() : * assumption : strat is a valid strategy */ -typedef void (*ZSTD_blockCompressor) (ZSTD_CCtx* ctx, const void* src, size_t srcSize); +typedef size_t (*ZSTD_blockCompressor) (ZSTD_CCtx* ctx, const void* src, size_t srcSize); static ZSTD_blockCompressor ZSTD_selectBlockCompressor(ZSTD_strategy strat, int extDict) { static const ZSTD_blockCompressor blockCompressor[2][(unsigned)ZSTD_btultra+1] = { @@ -2967,18 +3023,687 @@ static ZSTD_blockCompressor ZSTD_selectBlockCompressor(ZSTD_strategy strat, int return blockCompressor[extDict!=0][(U32)strat]; } +/*-************************************* +* Long distance matching +***************************************/ + +/** ZSTD_ldm_getSmallHash() : + * numBits should be <= 32 + * @return : the most significant numBits of value */ +static U32 ZSTD_ldm_getSmallHash(U64 value, U32 numBits) +{ + assert(numBits <= 32); + return (U32)(value >> (64 - numBits)); +} + +/** ZSTD_ldm_getChecksum() : + * numBitsToDiscard should be <= 32 + * @return : the next most significant 32 bits after numBitsToDiscard */ +static U32 ZSTD_ldm_getChecksum(U64 hash, U32 numBitsToDiscard) +{ + assert(numBitsToDiscard <= 32); + return (hash >> (64 - 32 - numBitsToDiscard)) & 0xFFFFFFFF; +} + +/** ZSTD_ldm_getTag() ; + * Given the hash, returns the most significant numTagBits bits + * after (32 + hbits) bits. + * + * If there are not enough bits remaining, return the last + * numTagBits bits. */ +static U32 ZSTD_ldm_getTag(U64 hash, U32 hbits, U32 numTagBits) +{ + if (32 - hbits < numTagBits) { + return hash & ((1 << numTagBits) - 1); + } else { + return (hash >> (32 - hbits - numTagBits)) & ((1 << numTagBits) - 1); + } +} + +/** ZSTD_ldm_getBucket() : + * Returns a pointer to the start of the bucket associated with hash. */ +static ldmEntry_t* ZSTD_ldm_getBucket(ldmState_t* ldmState, size_t hash) +{ + return ldmState->hashTable + (hash << ldmState->bucketLog); +} + +/** ZSTD_ldm_insertEntry() : + * Insert the entry with corresponding hash into the hash table */ +static void ZSTD_ldm_insertEntry(ldmState_t* ldmState, + size_t const hash, const ldmEntry_t entry) +{ + BYTE* const bucketOffsets = ldmState->bucketOffsets; + *(ZSTD_ldm_getBucket(ldmState, hash) + bucketOffsets[hash]) = entry; + bucketOffsets[hash]++; + bucketOffsets[hash] &= (1 << ldmState->bucketLog) - 1; +} + +/** ZSTD_ldm_makeEntryAndInsertByTag() : + * + * Gets the small hash, checksum, and tag from the rollingHash. + * + * If the tag matches (1 << ldmState->hashEveryLog)-1, then + * creates an ldmEntry from the offset, and inserts it into the hash table. + * + * hBits is the length of the small hash, which is the most significant hBits + * of rollingHash. The checksum is the next 32 most significant bits, followed + * by ldmState->hashEveryLog bits that make up the tag. */ +static void ZSTD_ldm_makeEntryAndInsertByTag(ldmState_t* ldmState, + U64 rollingHash, U32 hBits, + U32 const offset) +{ + U32 const tag = ZSTD_ldm_getTag(rollingHash, hBits, ldmState->hashEveryLog); + U32 const tagMask = (1 << ldmState->hashEveryLog) - 1; + if (tag == tagMask) { + U32 const hash = ZSTD_ldm_getSmallHash(rollingHash, hBits); + U32 const checksum = ZSTD_ldm_getChecksum(rollingHash, hBits); + ldmEntry_t entry; + entry.offset = offset; + entry.checksum = checksum; + ZSTD_ldm_insertEntry(ldmState, hash, entry); + } +} + +/** ZSTD_ldm_getRollingHash() : + * Get a 64-bit hash using the first len bytes from buf. + * + * Giving bytes s = s_1, s_2, ... s_k, the hash is defined to be + * H(s) = s_1*(a^(k-1)) + s_2*(a^(k-2)) + ... + s_k*(a^0) + * + * where the constant a is defined to be prime8bytes. + * + * The implementation adds an offset to each byte, so + * H(s) = (s_1 + HASH_CHAR_OFFSET)*(a^(k-1)) + ... */ +static U64 ZSTD_ldm_getRollingHash(const BYTE* buf, U32 len) +{ + U64 ret = 0; + U32 i; + for (i = 0; i < len; i++) { + ret *= prime8bytes; + ret += buf[i] + LDM_HASH_CHAR_OFFSET; + } + return ret; +} + +/** ZSTD_ldm_ipow() : + * Return base^exp. */ +static U64 ZSTD_ldm_ipow(U64 base, U64 exp) +{ + U64 ret = 1; + while (exp) { + if (exp & 1) { ret *= base; } + exp >>= 1; + base *= base; + } + return ret; +} + +/** ZSTD_ldm_updateHash() : + * Updates hash by removing toRemove and adding toAdd. + * + * Note: this currently relies on compiler optimization to avoid + * recalculating hashPower. */ +static U64 ZSTD_ldm_updateHash(U64 hash, BYTE toRemove, BYTE toAdd) +{ + U64 const hashPower = ZSTD_ldm_ipow(prime8bytes, LDM_MIN_MATCH_LENGTH - 1); + hash -= ((toRemove + LDM_HASH_CHAR_OFFSET) * hashPower); + hash *= prime8bytes; + hash += toAdd + LDM_HASH_CHAR_OFFSET; + return hash; +} + +/** ZSTD_ldm_countBackwardsMatch() : + * Returns the number of bytes that match backwards before pIn and pMatch. + * + * We count only bytes where pMatch >= pBase and pIn >= pAnchor. */ +static size_t ZSTD_ldm_countBackwardsMatch( + const BYTE* pIn, const BYTE* pAnchor, + const BYTE* pMatch, const BYTE* pBase) +{ + size_t matchLength = 0; + while (pIn > pAnchor && pMatch > pBase && pIn[-1] == pMatch[-1]) { + pIn--; + pMatch--; + matchLength++; + } + return matchLength; +} + +/** ZSTD_ldm_fillFastTables() : + * + * Fills the relevant tables for the ZSTD_fast and ZSTD_dfast strategies. + * This is similar to ZSTD_loadDictionaryContent. + * + * The tables for the other strategies are filled within their + * block compressors. */ +static size_t ZSTD_ldm_fillFastTables(ZSTD_CCtx* zc, const void* end) +{ + const BYTE* const iend = (const BYTE*)end; + const U32 mls = zc->appliedParams.cParams.searchLength; + + switch(zc->appliedParams.cParams.strategy) + { + case ZSTD_fast: + ZSTD_fillHashTable(zc, iend, mls); + zc->nextToUpdate = (U32)(iend - zc->base); + break; + + case ZSTD_dfast: + ZSTD_fillDoubleHashTable(zc, iend, mls); + zc->nextToUpdate = (U32)(iend - zc->base); + break; + + case ZSTD_greedy: + case ZSTD_lazy: + case ZSTD_lazy2: + case ZSTD_btlazy2: + case ZSTD_btopt: + case ZSTD_btultra: + break; + default: + assert(0); /* not possible : not a valid strategy id */ + } + + return 0; +} + +/** ZSTD_ldm_fillLdmHashTable() : + * + * Fills hashTable from (lastHashed + 1) to iend (non-inclusive). + * lastHash is the rolling hash that corresponds to lastHashed. + * + * Returns the rolling hash corresponding to position iend-1. */ +static U64 ZSTD_ldm_fillLdmHashTable(ldmState_t* state, + U64 lastHash, const BYTE* lastHashed, + const BYTE* iend, const BYTE* base, + U32 hBits) +{ + U64 rollingHash = lastHash; + const BYTE* cur = lastHashed + 1; + + while (cur < iend) { + rollingHash = ZSTD_ldm_updateHash(rollingHash, cur[-1], + cur[LDM_MIN_MATCH_LENGTH-1]); + ZSTD_ldm_makeEntryAndInsertByTag(state, + rollingHash, hBits, + (U32)(cur - base)); + ++cur; + } + return rollingHash; +} + + +/** ZSTD_ldm_limitTableUpdate() : + * + * Sets cctx->nextToUpdate to a position corresponding closer to anchor + * if it is far way + * (after a long match, only update tables a limited amount). */ +static void ZSTD_ldm_limitTableUpdate(ZSTD_CCtx* cctx, const BYTE* anchor) +{ + U32 const current = (U32)(anchor - cctx->base); + if (current > cctx->nextToUpdate + 1024) { + cctx->nextToUpdate = + current - MIN(512, current - cctx->nextToUpdate - 1024); + } +} + +/** ZSTD_compressBlock_ldm_generic() : + * + * This is a block compressor intended for long distance matching. + * + * The function searches for matches of length at least LDM_MIN_MATCH_LENGTH + * using a hash table in cctx->ldmState. Matches can be at a distance of + * up to LDM_WINDOW_LOG. + * + * Upon finding a match, the unmatched literals are compressed using a + * ZSTD_blockCompressor (depending on the strategy in the compression + * parameters), which stores the matched sequences. The "long distance" + * match is then stored with the remaining literals from the + * ZSTD_blockCompressor. */ +FORCE_INLINE +size_t ZSTD_compressBlock_ldm_generic(ZSTD_CCtx* cctx, + const void* src, size_t srcSize) +{ + ldmState_t* const ldmState = &(cctx->ldmState); + const U32 hBits = ldmState->hashLog - ldmState->bucketLog; + const U32 ldmBucketSize = (1 << ldmState->bucketLog); + const U32 ldmTagMask = (1 << ldmState->hashEveryLog) - 1; + seqStore_t* const seqStorePtr = &(cctx->seqStore); + const BYTE* const base = cctx->base; + const BYTE* const istart = (const BYTE*)src; + const BYTE* ip = istart; + const BYTE* anchor = istart; + const U32 lowestIndex = cctx->dictLimit; + const BYTE* const lowest = base + lowestIndex; + const BYTE* const iend = istart + srcSize; + const BYTE* const ilimit = iend - LDM_MIN_MATCH_LENGTH; + + const ZSTD_blockCompressor blockCompressor = + ZSTD_selectBlockCompressor(cctx->appliedParams.cParams.strategy, 0); + U32* const repToConfirm = seqStorePtr->repToConfirm; + U32 savedRep[ZSTD_REP_NUM]; + U64 rollingHash = 0; + const BYTE* lastHashed = NULL; + size_t i, lastLiterals; + + /* Save seqStorePtr->rep and copy repToConfirm */ + for (i = 0; i < ZSTD_REP_NUM; i++) + savedRep[i] = repToConfirm[i] = seqStorePtr->rep[i]; + + /* Main Search Loop */ + while (ip < ilimit) { /* < instead of <=, because repcode check at (ip+1) */ + size_t mLength; + U32 const current = (U32)(ip - base); + size_t forwardMatchLength = 0, backwardMatchLength = 0; + ldmEntry_t* bestEntry = NULL; + if (ip != istart) { + rollingHash = ZSTD_ldm_updateHash(rollingHash, lastHashed[0], + lastHashed[LDM_MIN_MATCH_LENGTH]); + } else { + rollingHash = ZSTD_ldm_getRollingHash(ip, LDM_MIN_MATCH_LENGTH); + } + lastHashed = ip; + + /* Do not insert and do not look for a match */ + if (ZSTD_ldm_getTag(rollingHash, hBits, ldmState->hashEveryLog) != + ldmTagMask) { + ip++; + continue; + } + + /* Get the best entry and compute the match lengths */ + { + ldmEntry_t* const bucket = + ZSTD_ldm_getBucket(ldmState, + ZSTD_ldm_getSmallHash(rollingHash, hBits)); + ldmEntry_t* cur; + size_t bestMatchLength = 0; + U32 const checksum = ZSTD_ldm_getChecksum(rollingHash, hBits); + + for (cur = bucket; cur < bucket + ldmBucketSize; ++cur) { + const BYTE* const pMatch = cur->offset + base; + size_t curForwardMatchLength, curBackwardMatchLength, + curTotalMatchLength; + if (cur->checksum != checksum || cur->offset <= lowestIndex) { + continue; + } + + curForwardMatchLength = ZSTD_count(ip, pMatch, iend); + if (curForwardMatchLength < LDM_MIN_MATCH_LENGTH) { + continue; + } + curBackwardMatchLength = ZSTD_ldm_countBackwardsMatch( + ip, anchor, pMatch, lowest); + curTotalMatchLength = curForwardMatchLength + + curBackwardMatchLength; + + if (curTotalMatchLength > bestMatchLength) { + bestMatchLength = curTotalMatchLength; + forwardMatchLength = curForwardMatchLength; + backwardMatchLength = curBackwardMatchLength; + bestEntry = cur; + } + } + } + + /* No match found -- continue searching */ + if (bestEntry == NULL) { + ZSTD_ldm_makeEntryAndInsertByTag(ldmState, rollingHash, + hBits, current); + ip++; + continue; + } + + /* Match found */ + mLength = forwardMatchLength + backwardMatchLength; + ip -= backwardMatchLength; + + /* Call the block compressor on the remaining literals */ + { + U32 const matchIndex = bestEntry->offset; + const BYTE* const match = base + matchIndex - backwardMatchLength; + U32 const offset = (U32)(ip - match); + + /* Overwrite rep codes */ + for (i = 0; i < ZSTD_REP_NUM; i++) + seqStorePtr->rep[i] = repToConfirm[i]; + + /* Fill tables for block compressor */ + ZSTD_ldm_limitTableUpdate(cctx, anchor); + ZSTD_ldm_fillFastTables(cctx, anchor); + + /* Call block compressor and get remaining literals */ + lastLiterals = blockCompressor(cctx, anchor, ip - anchor); + cctx->nextToUpdate = (U32)(ip - base); + + /* Update repToConfirm with the new offset */ + for (i = ZSTD_REP_NUM - 1; i > 0; i--) + repToConfirm[i] = repToConfirm[i-1]; + repToConfirm[0] = offset; + + /* Store the sequence with the leftover literals */ + ZSTD_storeSeq(seqStorePtr, lastLiterals, ip - lastLiterals, + offset + ZSTD_REP_MOVE, mLength - MINMATCH); + } + + /* Insert the current entry into the hash table */ + ZSTD_ldm_makeEntryAndInsertByTag(ldmState, rollingHash, hBits, + (U32)(lastHashed - base)); + + assert(ip + backwardMatchLength == lastHashed); + + /* Fill the hash table from lastHashed+1 to ip+mLength*/ + /* Heuristic: don't need to fill the entire table at end of block */ + if (ip + mLength < ilimit) { + rollingHash = ZSTD_ldm_fillLdmHashTable( + ldmState, rollingHash, lastHashed, + ip + mLength, base, hBits); + lastHashed = ip + mLength - 1; + } + ip += mLength; + anchor = ip; + + /* Check immediate repcode */ + while ( (ip < ilimit) + && ( (repToConfirm[1] > 0) + && (MEM_read32(ip) == MEM_read32(ip - repToConfirm[1])) )) { + + size_t const rLength = ZSTD_count(ip+4, ip+4-repToConfirm[1], + iend) + 4; + /* Swap repToConfirm[1] <=> repToConfirm[0] */ + { + U32 const tmpOff = repToConfirm[1]; + repToConfirm[1] = repToConfirm[0]; + repToConfirm[0] = tmpOff; + } + + ZSTD_storeSeq(seqStorePtr, 0, anchor, 0, rLength-MINMATCH); + + /* Fill the hash table from lastHashed+1 to ip+rLength*/ + if (ip + rLength < ilimit) { + rollingHash = ZSTD_ldm_fillLdmHashTable( + ldmState, rollingHash, lastHashed, + ip + rLength, base, hBits); + lastHashed = ip + rLength - 1; + } + ip += rLength; + anchor = ip; + + continue; /* faster when present ... (?) */ + } + } + + /* Overwrite rep */ + for (i = 0; i < ZSTD_REP_NUM; i++) + seqStorePtr->rep[i] = repToConfirm[i]; + + ZSTD_ldm_limitTableUpdate(cctx, anchor); + ZSTD_ldm_fillFastTables(cctx, anchor); + + lastLiterals = blockCompressor(cctx, anchor, iend - anchor); + cctx->nextToUpdate = (U32)(ip - base); + + /* Restore seqStorePtr->rep */ + for (i = 0; i < ZSTD_REP_NUM; i++) + seqStorePtr->rep[i] = savedRep[i]; + + /* Return the last literals size */ + return lastLiterals; +} + +static size_t ZSTD_compressBlock_ldm(ZSTD_CCtx* ctx, + const void* src, size_t srcSize) +{ + return ZSTD_compressBlock_ldm_generic(ctx, src, srcSize); +} + +static size_t ZSTD_compressBlock_ldm_extDict_generic( + ZSTD_CCtx* ctx, + const void* src, size_t srcSize) +{ + ldmState_t* ldmState = &(ctx->ldmState); + const U32 hBits = ldmState->hashLog - ldmState->bucketLog; + const U32 ldmBucketSize = (1 << ldmState->bucketLog); + const U32 ldmTagMask = (1 << ldmState->hashEveryLog) - 1; + seqStore_t* const seqStorePtr = &(ctx->seqStore); + const BYTE* const base = ctx->base; + const BYTE* const dictBase = ctx->dictBase; + const BYTE* const istart = (const BYTE*)src; + const BYTE* ip = istart; + const BYTE* anchor = istart; + const U32 lowestIndex = ctx->lowLimit; + const BYTE* const dictStart = dictBase + lowestIndex; + const U32 dictLimit = ctx->dictLimit; + const BYTE* const lowPrefixPtr = base + dictLimit; + const BYTE* const dictEnd = dictBase + dictLimit; + const BYTE* const iend = istart + srcSize; + const BYTE* const ilimit = iend - LDM_MIN_MATCH_LENGTH; + + const ZSTD_blockCompressor blockCompressor = + ZSTD_selectBlockCompressor(ctx->appliedParams.cParams.strategy, 1); + U32* const repToConfirm = seqStorePtr->repToConfirm; + U32 savedRep[ZSTD_REP_NUM]; + U64 rollingHash = 0; + const BYTE* lastHashed = NULL; + size_t i, lastLiterals; + + /* Save seqStorePtr->rep and copy repToConfirm */ + for (i = 0; i < ZSTD_REP_NUM; i++) { + savedRep[i] = repToConfirm[i] = seqStorePtr->rep[i]; + } + + /* Search Loop */ + while (ip < ilimit) { /* < instead of <=, because (ip+1) */ + size_t mLength; + const U32 current = (U32)(ip-base); + size_t forwardMatchLength = 0, backwardMatchLength = 0; + ldmEntry_t* bestEntry = NULL; + if (ip != istart) { + rollingHash = ZSTD_ldm_updateHash(rollingHash, lastHashed[0], + lastHashed[LDM_MIN_MATCH_LENGTH]); + } else { + rollingHash = ZSTD_ldm_getRollingHash(ip, LDM_MIN_MATCH_LENGTH); + } + lastHashed = ip; + + if (ZSTD_ldm_getTag(rollingHash, hBits, ldmState->hashEveryLog) != + ldmTagMask) { + /* Don't insert and don't look for a match */ + ip++; + continue; + } + + /* Get the best entry and compute the match lengths */ + { + ldmEntry_t* const bucket = + ZSTD_ldm_getBucket(ldmState, + ZSTD_ldm_getSmallHash(rollingHash, hBits)); + ldmEntry_t* cur; + size_t bestMatchLength = 0; + U32 const checksum = ZSTD_ldm_getChecksum(rollingHash, hBits); + + for (cur = bucket; cur < bucket + ldmBucketSize; ++cur) { + const BYTE* const curMatchBase = + cur->offset < dictLimit ? dictBase : base; + const BYTE* const pMatch = curMatchBase + cur->offset; + const BYTE* const matchEnd = + cur->offset < dictLimit ? dictEnd : iend; + const BYTE* const lowMatchPtr = + cur->offset < dictLimit ? dictStart : lowPrefixPtr; + size_t curForwardMatchLength, curBackwardMatchLength, + curTotalMatchLength; + + if (cur->checksum != checksum || cur->offset <= lowestIndex) { + continue; + } + + curForwardMatchLength = ZSTD_count_2segments( + ip, pMatch, iend, + matchEnd, lowPrefixPtr); + if (curForwardMatchLength < LDM_MIN_MATCH_LENGTH) { + continue; + } + curBackwardMatchLength = ZSTD_ldm_countBackwardsMatch( + ip, anchor, pMatch, lowMatchPtr); + curTotalMatchLength = curForwardMatchLength + + curBackwardMatchLength; + + if (curTotalMatchLength > bestMatchLength) { + bestMatchLength = curTotalMatchLength; + forwardMatchLength = curForwardMatchLength; + backwardMatchLength = curBackwardMatchLength; + bestEntry = cur; + } + } + } + + /* No match found -- continue searching */ + if (bestEntry == NULL) { + ZSTD_ldm_makeEntryAndInsertByTag(ldmState, rollingHash, hBits, + (U32)(lastHashed - base)); + ip++; + continue; + } + + /* Match found */ + mLength = forwardMatchLength + backwardMatchLength; + ip -= backwardMatchLength; + + /* Call the block compressor on the remaining literals */ + { + U32 const matchIndex = bestEntry->offset; + U32 const offset = current - matchIndex; + + /* Overwrite rep codes */ + for (i = 0; i < ZSTD_REP_NUM; i++) + seqStorePtr->rep[i] = repToConfirm[i]; + + /* Fill the hash table for the block compressor */ + ZSTD_ldm_limitTableUpdate(ctx, anchor); + ZSTD_ldm_fillFastTables(ctx, anchor); + + /* Call block compressor and get remaining literals */ + lastLiterals = blockCompressor(ctx, anchor, ip - anchor); + ctx->nextToUpdate = (U32)(ip - base); + + /* Update repToConfirm with the new offset */ + for (i = ZSTD_REP_NUM - 1; i > 0; i--) + repToConfirm[i] = repToConfirm[i-1]; + repToConfirm[0] = offset; + + /* Store the sequence with the leftover literals */ + ZSTD_storeSeq(seqStorePtr, lastLiterals, ip - lastLiterals, + offset + ZSTD_REP_MOVE, mLength - MINMATCH); + } + + /* Insert the current entry into the hash table */ + ZSTD_ldm_makeEntryAndInsertByTag(ldmState, rollingHash, hBits, + (U32)(lastHashed - base)); + + /* Fill the hash table from lastHashed+1 to ip+mLength */ + assert(ip + backwardMatchLength == lastHashed); + if (ip + mLength < ilimit) { + rollingHash = ZSTD_ldm_fillLdmHashTable( + ldmState, rollingHash, lastHashed, + ip + mLength, base, hBits); + lastHashed = ip + mLength - 1; + } + ip += mLength; + anchor = ip; + + /* check immediate repcode */ + while (ip < ilimit) { + U32 const current2 = (U32)(ip-base); + U32 const repIndex2 = current2 - repToConfirm[1]; + const BYTE* repMatch2 = repIndex2 < dictLimit ? + dictBase + repIndex2 : base + repIndex2; + if ( (((U32)((dictLimit-1) - repIndex2) >= 3) & + (repIndex2 > lowestIndex)) /* intentional overflow */ + && (MEM_read32(repMatch2) == MEM_read32(ip)) ) { + const BYTE* const repEnd2 = repIndex2 < dictLimit ? + dictEnd : iend; + size_t const repLength2 = + ZSTD_count_2segments(ip+4, repMatch2+4, iend, + repEnd2, lowPrefixPtr) + 4; + + U32 tmpOffset = repToConfirm[1]; + repToConfirm[1] = repToConfirm[0]; + repToConfirm[0] = tmpOffset; + + ZSTD_storeSeq(seqStorePtr, 0, anchor, 0, repLength2-MINMATCH); + + /* Fill the hash table from lastHashed+1 to ip+repLength2*/ + if (ip + repLength2 < ilimit) { + rollingHash = ZSTD_ldm_fillLdmHashTable( + ldmState, rollingHash, lastHashed, + ip + repLength2, base, hBits); + lastHashed = ip + repLength2 - 1; + } + ip += repLength2; + anchor = ip; + continue; + } + break; + } + } + + /* Overwrite rep */ + for (i = 0; i < ZSTD_REP_NUM; i++) + seqStorePtr->rep[i] = repToConfirm[i]; + + ZSTD_ldm_limitTableUpdate(ctx, anchor); + ZSTD_ldm_fillFastTables(ctx, anchor); + + /* Call the block compressor one last time on the last literals */ + lastLiterals = blockCompressor(ctx, anchor, iend - anchor); + ctx->nextToUpdate = (U32)(ip - base); + + /* Restore seqStorePtr->rep */ + for (i = 0; i < ZSTD_REP_NUM; i++) + seqStorePtr->rep[i] = savedRep[i]; + + /* Return the last literals size */ + return lastLiterals; +} + +static size_t ZSTD_compressBlock_ldm_extDict(ZSTD_CCtx* ctx, + const void* src, size_t srcSize) +{ + return ZSTD_compressBlock_ldm_extDict_generic(ctx, src, srcSize); +} + +static void ZSTD_storeLastLiterals(seqStore_t* seqStorePtr, + const BYTE* anchor, size_t lastLLSize) +{ + memcpy(seqStorePtr->lit, anchor, lastLLSize); + seqStorePtr->lit += lastLLSize; +} static size_t ZSTD_compressBlock_internal(ZSTD_CCtx* zc, void* dst, size_t dstCapacity, const void* src, size_t srcSize) { - ZSTD_blockCompressor const blockCompressor = ZSTD_selectBlockCompressor(zc->appliedParams.cParams.strategy, zc->lowLimit < zc->dictLimit); const BYTE* const base = zc->base; const BYTE* const istart = (const BYTE*)src; const U32 current = (U32)(istart-base); + size_t lastLLSize; + const BYTE* anchor; + const ZSTD_blockCompressor blockCompressor = + zc->ldmState.ldmEnable ? + (zc->lowLimit < zc->dictLimit ? ZSTD_compressBlock_ldm_extDict : + ZSTD_compressBlock_ldm) : + ZSTD_selectBlockCompressor(zc->appliedParams.cParams.strategy, + zc->lowLimit < zc->dictLimit); + if (srcSize < MIN_CBLOCK_SIZE+ZSTD_blockHeaderSize+1) return 0; /* don't even attempt compression below a certain srcSize */ ZSTD_resetSeqStore(&(zc->seqStore)); if (current > zc->nextToUpdate + 384) zc->nextToUpdate = current - MIN(192, (U32)(current - zc->nextToUpdate - 384)); /* limited update after finding a very long match */ - blockCompressor(zc, src, srcSize); + + lastLLSize = blockCompressor(zc, src, srcSize); + + /* Last literals */ + anchor = (const BYTE*)src + srcSize - lastLLSize; + ZSTD_storeLastLiterals(&zc->seqStore, anchor, lastLLSize); + return ZSTD_compressSequences(&zc->seqStore, zc->entropy, &zc->appliedParams.cParams, dst, dstCapacity, srcSize); } @@ -3203,7 +3928,6 @@ static size_t ZSTD_loadDictionaryContent(ZSTD_CCtx* zc, const void* src, size_t case ZSTD_fast: ZSTD_fillHashTable (zc, iend, zc->appliedParams.cParams.searchLength); break; - case ZSTD_dfast: ZSTD_fillDoubleHashTable (zc, iend, zc->appliedParams.cParams.searchLength); break; diff --git a/lib/compress/zstd_opt.h b/lib/compress/zstd_opt.h index 53e806eb..575cfa66 100644 --- a/lib/compress/zstd_opt.h +++ b/lib/compress/zstd_opt.h @@ -413,8 +413,9 @@ static U32 ZSTD_BtGetAllMatches_selectMLS_extDict ( * Optimal parser *********************************/ FORCE_INLINE -void ZSTD_compressBlock_opt_generic(ZSTD_CCtx* ctx, - const void* src, size_t srcSize, const int ultra) +size_t ZSTD_compressBlock_opt_generic(ZSTD_CCtx* ctx, + const void* src, size_t srcSize, + const int ultra) { seqStore_t* seqStorePtr = &(ctx->seqStore); optState_t* optStatePtr = &(ctx->optState); @@ -654,17 +655,15 @@ _storeSequence: /* cur, last_pos, best_mlen, best_off have to be set */ /* Save reps for next block */ { int i; for (i=0; irepToConfirm[i] = rep[i]; } - /* Last Literals */ - { size_t const lastLLSize = iend - anchor; - memcpy(seqStorePtr->lit, anchor, lastLLSize); - seqStorePtr->lit += lastLLSize; - } + /* Return the last literals size */ + return iend - anchor; } FORCE_INLINE -void ZSTD_compressBlock_opt_extDict_generic(ZSTD_CCtx* ctx, - const void* src, size_t srcSize, const int ultra) +size_t ZSTD_compressBlock_opt_extDict_generic(ZSTD_CCtx* ctx, + const void* src, size_t srcSize, + const int ultra) { seqStore_t* seqStorePtr = &(ctx->seqStore); optState_t* optStatePtr = &(ctx->optState); @@ -928,11 +927,8 @@ _storeSequence: /* cur, last_pos, best_mlen, best_off have to be set */ /* Save reps for next block */ { int i; for (i=0; irepToConfirm[i] = rep[i]; } - /* Last Literals */ - { size_t lastLLSize = iend - anchor; - memcpy(seqStorePtr->lit, anchor, lastLLSize); - seqStorePtr->lit += lastLLSize; - } + /* Return the last literals size */ + return iend - anchor; } #endif /* ZSTD_OPT_H_91842398743 */ diff --git a/lib/zstd.h b/lib/zstd.h index c1196440..c9fa34f3 100644 --- a/lib/zstd.h +++ b/lib/zstd.h @@ -978,7 +978,9 @@ typedef enum { /* advanced parameters - may not remain available after API update */ ZSTD_p_forceMaxWindow=1100, /* Force back-reference distances to remain < windowSize, * even when referencing into Dictionary content (default:0) */ - + ZSTD_p_longDistanceMatching, /* Enable long distance matching. + * This increases the memory usage as well as the + * window size. */ } ZSTD_cParameter; diff --git a/programs/bench.c b/programs/bench.c index 7731d079..a2c4efcf 100644 --- a/programs/bench.c +++ b/programs/bench.c @@ -129,7 +129,10 @@ void BMK_setNbThreads(unsigned nbThreads) { #endif g_nbThreads = nbThreads; } - +static U32 g_ldmFlag = 0; +void BMK_setLdmFlag(unsigned ldmFlag) { + g_ldmFlag = ldmFlag; +} /* ******************************************************** * Bench functions @@ -271,6 +274,7 @@ static int BMK_benchMem(const void* srcBuffer, size_t srcSize, ZSTD_CCtx_setParameter(ctx, ZSTD_p_searchLog, comprParams->searchLog); ZSTD_CCtx_setParameter(ctx, ZSTD_p_minMatch, comprParams->searchLength); ZSTD_CCtx_setParameter(ctx, ZSTD_p_targetLength, comprParams->targetLength); + ZSTD_CCtx_setParameter(ctx, ZSTD_p_longDistanceMatching, g_ldmFlag); ZSTD_CCtx_setParameter(ctx, ZSTD_p_compressionStrategy, comprParams->strategy); ZSTD_CCtx_loadDictionary(ctx, dictBuffer, dictBufferSize); #else diff --git a/programs/bench.h b/programs/bench.h index 77a527f8..03f56d06 100644 --- a/programs/bench.h +++ b/programs/bench.h @@ -25,5 +25,6 @@ void BMK_setNbThreads(unsigned nbThreads); void BMK_setNotificationLevel(unsigned level); void BMK_setAdditionalParam(int additionalParam); void BMK_setDecodeOnlyMode(unsigned decodeFlag); +void BMK_setLdmFlag(unsigned ldmFlag); #endif /* BENCH_H_121279284357 */ diff --git a/programs/fileio.c b/programs/fileio.c index 1dd8008e..8d024a9b 100644 --- a/programs/fileio.c +++ b/programs/fileio.c @@ -213,6 +213,10 @@ void FIO_setOverlapLog(unsigned overlapLog){ DISPLAYLEVEL(2, "Setting overlapLog is useless in single-thread mode \n"); g_overlapLog = overlapLog; } +static U32 g_ldmFlag = 0; +void FIO_setLdmFlag(unsigned ldmFlag) { + g_ldmFlag = (ldmFlag>0); +} /*-************************************* @@ -407,6 +411,8 @@ static cRess_t FIO_createCResources(const char* dictFileName, int cLevel, CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_p_minMatch, comprParams->searchLength) ); CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_p_targetLength, comprParams->targetLength) ); CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_p_compressionStrategy, (U32)comprParams->strategy) ); + /* long distance matching */ + CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_p_longDistanceMatching, g_ldmFlag) ); /* multi-threading */ CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_p_nbThreads, g_nbThreads) ); /* dictionary */ diff --git a/programs/fileio.h b/programs/fileio.h index 9d9167df..06cf414d 100644 --- a/programs/fileio.h +++ b/programs/fileio.h @@ -56,6 +56,7 @@ void FIO_setMemLimit(unsigned memLimit); void FIO_setNbThreads(unsigned nbThreads); void FIO_setBlockSize(unsigned blockSize); void FIO_setOverlapLog(unsigned overlapLog); +void FIO_setLdmFlag(unsigned ldmFlag); /*-************************************* diff --git a/programs/zstdcli.c b/programs/zstdcli.c index b1268c1f..cf0710f7 100644 --- a/programs/zstdcli.c +++ b/programs/zstdcli.c @@ -152,6 +152,7 @@ static int usage_advanced(const char* programName) #endif DISPLAY( " -M# : Set a memory usage limit for decompression \n"); DISPLAY( "--list : list information about a zstd compressed file \n"); + DISPLAY( "--long : enable long distance matching\n"); DISPLAY( "-- : All arguments after \"--\" are treated as files \n"); #ifndef ZSTD_NODICT DISPLAY( "\n"); @@ -333,7 +334,8 @@ int main(int argCount, const char* argv[]) ultra=0, lastCommand = 0, nbThreads = 1, - setRealTimePrio = 0; + setRealTimePrio = 0, + ldmFlag = 0; unsigned bench_nbSeconds = 3; /* would be better if this value was synchronized from bench */ size_t blockSize = 0; zstd_operation_mode operation = zom_compress; @@ -440,6 +442,7 @@ int main(int argCount, const char* argv[]) #ifdef ZSTD_LZ4COMPRESS if (!strcmp(argument, "--format=lz4")) { suffix = LZ4_EXTENSION; FIO_setCompressionType(FIO_lz4Compression); continue; } #endif + if (!strcmp(argument, "--long")) { ldmFlag = 1; continue; } /* long commands with arguments */ #ifndef ZSTD_NODICT @@ -690,6 +693,7 @@ int main(int argCount, const char* argv[]) BMK_setBlockSize(blockSize); BMK_setNbThreads(nbThreads); BMK_setNbSeconds(bench_nbSeconds); + BMK_setLdmFlag(ldmFlag); BMK_benchFiles(filenameTable, filenameIdx, dictFileName, cLevel, cLevelLast, &compressionParams, setRealTimePrio); #endif (void)bench_nbSeconds; @@ -757,6 +761,7 @@ int main(int argCount, const char* argv[]) #ifndef ZSTD_NOCOMPRESS FIO_setNbThreads(nbThreads); FIO_setBlockSize((U32)blockSize); + FIO_setLdmFlag(ldmFlag); if (g_overlapLog!=OVERLAP_LOG_DEFAULT) FIO_setOverlapLog(g_overlapLog); if ((filenameIdx==1) && outFileName) operationResult = FIO_compressFilename(outFileName, filenameTable[0], dictFileName, cLevel, &compressionParams); diff --git a/tests/fuzzer.c b/tests/fuzzer.c index 439ab39d..ca67483d 100644 --- a/tests/fuzzer.c +++ b/tests/fuzzer.c @@ -440,8 +440,6 @@ static int basicUnitTests(U32 seed, double compressibility) free(staticDCtxBuffer); } - - /* ZSTDMT simple MT compression test */ DISPLAYLEVEL(4, "test%3i : create ZSTDMT CCtx : ", testNb++); { ZSTDMT_CCtx* mtctx = ZSTDMT_createCCtx(2); @@ -1342,6 +1340,7 @@ static int fuzzerTests(U32 seed, U32 nbTests, unsigned startTest, U32 const maxD dictSize = FUZ_rLogLength(&lseed, dictLog); /* needed also for decompression */ dict = srcBuffer + (FUZ_rand(&lseed) % (srcBufferSize - dictSize)); + if (FUZ_rand(&lseed) & 0xF) { CHECK_Z ( ZSTD_compressBegin_usingDict(refCtx, dict, dictSize, cLevel) ); } else { @@ -1350,6 +1349,7 @@ static int fuzzerTests(U32 seed, U32 nbTests, unsigned startTest, U32 const maxD !(FUZ_rand(&lseed)&3) /* contentChecksumFlag*/, 0 /*NodictID*/ }; /* note : since dictionary is fake, dictIDflag has no impact */ ZSTD_parameters const p = FUZ_makeParams(cPar, fPar); + CHECK_Z ( ZSTD_compressBegin_advanced(refCtx, dict, dictSize, p, 0) ); } CHECK_Z( ZSTD_copyCCtx(ctx, refCtx, 0) ); diff --git a/tests/playTests.sh b/tests/playTests.sh index 77853b1a..8fb2768c 100755 --- a/tests/playTests.sh +++ b/tests/playTests.sh @@ -544,6 +544,15 @@ roundTripTest -g516K 19 # btopt fileRoundTripTest -g500K +$ECHO "\n**** zstd long distance matching round-trip tests **** " +roundTripTest -g0 "2 --long" +roundTripTest -g1000K "1 --long" +roundTripTest -g517K "6 --long" +roundTripTest -g516K "16 --long" +roundTripTest -g518K "19 --long" +fileRoundTripTest -g5M "3 --long" + + if [ -n "$hasMT" ] then $ECHO "\n**** zstdmt round-trip tests **** " @@ -551,6 +560,9 @@ then roundTripTest -g8M "3 -T2" roundTripTest -g8000K "2 --threads=2" fileRoundTripTest -g4M "19 -T2 -B1M" + + $ECHO "\n**** zstdmt long distance matching round-trip tests **** " + roundTripTest -g8M "3 --long -T2" else $ECHO "\n**** no multithreading, skipping zstdmt tests **** " fi @@ -639,6 +651,15 @@ roundTripTest -g6000000000 -P99 1 fileRoundTripTest -g4193M -P99 1 +$ECHO "\n**** zstd long, long distance matching round-trip tests **** " +roundTripTest -g0 "2 --long" +roundTripTest -g270000000 "1 --long" +roundTripTest -g140000000 -P60 "5 --long" +roundTripTest -g70000000 -P70 "8 --long" +roundTripTest -g18000001 -P80 "18 --long" +fileRoundTripTest -g4100M -P99 "1 --long" + + if [ -n "$hasMT" ] then $ECHO "\n**** zstdmt long round-trip tests **** " @@ -646,6 +667,7 @@ then roundTripTest -g6000000000 -P99 "1 -T2" roundTripTest -g1500000000 -P97 "1 -T999" fileRoundTripTest -g4195M -P98 " -T0" + roundTripTest -g1500000000 -P97 "1 --long -T999" else $ECHO "\n**** no multithreading, skipping zstdmt tests **** " fi diff --git a/tests/zstreamtest.c b/tests/zstreamtest.c index 8be6a591..3baa10ef 100644 --- a/tests/zstreamtest.c +++ b/tests/zstreamtest.c @@ -1380,6 +1380,8 @@ static int fuzzerTests_newAPI(U32 seed, U32 nbTests, unsigned startTest, double if (FUZ_rand(&lseed) & 1) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_minMatch, cParams.searchLength, useOpaqueAPI) ); if (FUZ_rand(&lseed) & 1) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_targetLength, cParams.targetLength, useOpaqueAPI) ); + if (FUZ_rand(&lseed) & 1) CHECK_Z( ZSTD_CCtx_setParameter(zc, ZSTD_p_longDistanceMatching, FUZ_rand(&lseed) & 63) ); + /* unconditionally set, to be sync with decoder */ /* mess with frame parameters */ if (FUZ_rand(&lseed) & 1) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_checksumFlag, FUZ_rand(&lseed) & 1, useOpaqueAPI) ); From 8081becadce1d706b2eb17ebdc278651d4dcf709 Mon Sep 17 00:00:00 2001 From: Stella Lau Date: Thu, 31 Aug 2017 15:40:16 -0700 Subject: [PATCH 02/15] Add long distance matching as a CCtxParam --- lib/common/zstd_internal.h | 7 ++-- lib/compress/zstd_compress.c | 73 ++++++++++++++++++++-------------- lib/compress/zstdmt_compress.c | 2 + lib/zstd.h | 8 ++-- programs/bench.c | 2 +- programs/fileio.c | 7 ++-- tests/fuzzer.c | 5 ++- tests/zstreamtest.c | 2 +- 8 files changed, 64 insertions(+), 42 deletions(-) diff --git a/lib/common/zstd_internal.h b/lib/common/zstd_internal.h index 49b21c2d..4488ac35 100644 --- a/lib/common/zstd_internal.h +++ b/lib/common/zstd_internal.h @@ -281,11 +281,10 @@ typedef struct { typedef struct { ldmEntry_t* hashTable; - BYTE* bucketOffsets; - U32 ldmEnable; /* 1 if enable long distance matching */ + BYTE* bucketOffsets; /* next position in bucket to insert entry */ U32 hashLog; /* log size of hashTable */ U32 bucketLog; /* log number of buckets, at most 4 */ - U32 hashEveryLog; + U32 hashEveryLog; /* log number of entries to skip */ } ldmState_t; typedef struct { @@ -313,6 +312,8 @@ struct ZSTD_CCtx_params_s { unsigned jobSize; unsigned overlapSizeLog; + U32 enableLdm; /* 1 if enable long distance matching */ + /* For use with createCCtxParams() and freeCCtxParams() only */ ZSTD_customMem customMem; diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 331b2120..42d23759 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -53,6 +53,7 @@ size_t ZSTD_compressBound(size_t srcSize) { return srcSize + (srcSize >> 8) + margin; } + /*-************************************* * Sequence storage ***************************************/ @@ -362,14 +363,11 @@ size_t ZSTD_CCtx_setParameter(ZSTD_CCtx* cctx, ZSTD_cParameter param, unsigned v return ZSTD_CCtxParam_setParameter(&cctx->requestedParams, param, value); case ZSTD_p_longDistanceMatching: - /* TODO */ if (cctx->cdict) return ERROR(stage_wrong); - cctx->ldmState.ldmEnable = value>0; if (value != 0) { ZSTD_cLevelToCParams(cctx); - cctx->requestedParams.cParams.windowLog = LDM_WINDOW_LOG; } - return 0; + return ZSTD_CCtxParam_setParameter(&cctx->requestedParams, param, value); default: return ERROR(parameter_unsupported); } @@ -471,8 +469,12 @@ size_t ZSTD_CCtxParam_setParameter( return ZSTDMT_CCtxParam_setMTCtxParameter(params, ZSTDMT_p_overlapSectionLog, value); case ZSTD_p_longDistanceMatching : - /* TODO */ - return ERROR(parameter_unsupported); + params->enableLdm = value>0; + if (value != 0) { + ZSTD_cLevelToCCtxParams(params); + params->cParams.windowLog = LDM_WINDOW_LOG; + } + return 0; default: return ERROR(parameter_unsupported); } @@ -509,6 +511,9 @@ size_t ZSTD_CCtx_setParametersUsingCCtxParams( cctx, ZSTD_p_overlapSizeLog, params->overlapSizeLog) ); } + /* Copy long distance matching parameter */ + cctx->requestedParams.enableLdm = params->enableLdm; + /* customMem is used only for create/free params and can be ignored */ return 0; } @@ -675,6 +680,16 @@ ZSTD_compressionParameters ZSTD_adjustCParams(ZSTD_compressionParameters cPar, u return ZSTD_adjustCParams_internal(cPar, srcSize, dictSize); } +/* Estimate the space needed for long distance matching tables. */ +static size_t ZSTD_ldm_getTableSize(U32 ldmHashLog, U32 bucketLog) { + size_t const ldmHSize = ((size_t)1) << ldmHashLog; + size_t const ldmBucketLog = + MIN(bucketLog, LDM_BUCKET_SIZE_LOG_MAX); + size_t const ldmBucketSize = + ((size_t)1) << (ldmHashLog - ldmBucketLog); + return ldmBucketSize + (ldmHSize * (sizeof(ldmEntry_t))); +} + size_t ZSTD_estimateCCtxSize_advanced_usingCCtxParams(const ZSTD_CCtx_params* params) { /* Estimate CCtx size is supported for single-threaded compression only. */ @@ -699,8 +714,10 @@ size_t ZSTD_estimateCCtxSize_advanced_usingCCtxParams(const ZSTD_CCtx_params* pa + (ZSTD_OPT_NUM+1)*(sizeof(ZSTD_match_t) + sizeof(ZSTD_optimal_t)); size_t const optSpace = ((cParams.strategy == ZSTD_btopt) || (cParams.strategy == ZSTD_btultra)) ? optBudget : 0; - /* TODO: Long distance matching is not suported */ - size_t const ldmSpace = 0; + /* Ldm parameters can not currently be changed */ + size_t const ldmSpace = params->enableLdm ? + ZSTD_ldm_getTableSize(LDM_HASH_LOG, LDM_BUCKET_SIZE_LOG) : 0; + size_t const neededSpace = entropySpace + tableSpace + tokenSpace + optSpace + ldmSpace; @@ -762,7 +779,8 @@ static U32 ZSTD_equivalentCParams(ZSTD_compressionParameters cParams1, static U32 ZSTD_equivalentParams(ZSTD_CCtx_params params1, ZSTD_CCtx_params params2) { - return ZSTD_equivalentCParams(params1.cParams, params2.cParams); + return ZSTD_equivalentCParams(params1.cParams, params2.cParams) && + params1.enableLdm == params2.enableLdm; } /*! ZSTD_continueCCtx() : @@ -803,9 +821,7 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, assert(!ZSTD_isError(ZSTD_checkCParams(params.cParams))); if (crp == ZSTDcrp_continue) { - /* TODO: For now, reset if long distance matching is enabled */ - if (ZSTD_equivalentParams(params, zc->appliedParams) && - !zc->ldmState.ldmEnable) { + if (ZSTD_equivalentParams(params, zc->appliedParams)) { DEBUGLOG(5, "ZSTD_equivalentParams()==1"); zc->entropy->hufCTable_repeatMode = HUF_repeat_none; zc->entropy->offcode_repeatMode = FSE_repeat_none; @@ -838,13 +854,7 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, size_t const buffInSize = (zbuff==ZSTDb_buffered) ? ((size_t)1 << params.cParams.windowLog) + blockSize : 0; void* ptr; - size_t const ldmHSize = ((size_t)1) << zc->ldmState.hashLog; - size_t const ldmBucketSize = - ((size_t)1) << (zc->ldmState.hashLog - zc->ldmState.bucketLog); - size_t const ldmPotentialSpace = - ldmBucketSize + (ldmHSize * (sizeof(ldmEntry_t))); - size_t const ldmSpace = zc->ldmState.ldmEnable ? - ldmPotentialSpace : 0; + size_t const ldmSpace = params.enableLdm ? ZSTD_ldm_getTableSize(zc->ldmState.hashLog, zc->ldmState.bucketLog) : 0; /* Check if workSpace is large enough, alloc a new one if needed */ { size_t const entropySpace = sizeof(ZSTD_entropyCTables_t); @@ -923,8 +933,11 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, } /* ldm space */ - if (zc->ldmState.ldmEnable) { - if (crp!=ZSTDcrp_noMemset) memset(ptr, 0, ldmSpace); + if (params.enableLdm) { + size_t const ldmHSize = ((size_t)1) << zc->ldmState.hashLog; + size_t const ldmBucketSize = + ((size_t)1) << (zc->ldmState.hashLog - zc->ldmState.bucketLog); + memset(ptr, 0, ldmSpace); assert(((size_t)ptr & 3) == 0); /* ensure ptr is properly aligned */ zc->ldmState.hashTable = (ldmEntry_t*)ptr; ptr = zc->ldmState.hashTable + ldmHSize; @@ -1047,7 +1060,7 @@ static void ZSTD_reduceTable (U32* const table, U32 const size, U32 const reduce /*! ZSTD_ldm_reduceTable() : * reduce table indexes by `reducerValue` */ static void ZSTD_ldm_reduceTable(ldmEntry_t* const table, U32 const size, - U32 const reducerValue) + U32 const reducerValue) { U32 u; for (u = 0; u < size; u++) { @@ -1069,8 +1082,8 @@ static void ZSTD_reduceIndex (ZSTD_CCtx* zc, const U32 reducerValue) { U32 const h3Size = (zc->hashLog3) ? 1 << zc->hashLog3 : 0; ZSTD_reduceTable(zc->hashTable3, h3Size, reducerValue); } - { if (zc->ldmState.ldmEnable) { - U32 const ldmHSize = 1 << LDM_HASH_LOG; + { if (zc->appliedParams.enableLdm) { + U32 const ldmHSize = 1 << zc->ldmState.hashLog; ZSTD_ldm_reduceTable(zc->ldmState.hashTable, ldmHSize, reducerValue); } } @@ -1683,6 +1696,7 @@ static size_t ZSTD_hashPtr(const void* p, U32 hBits, U32 mls) } } + /*-************************************* * Fast Scan ***************************************/ @@ -1751,6 +1765,7 @@ size_t ZSTD_compressBlock_fast_generic(ZSTD_CCtx* cctx, while (((ip>anchor) & (match>lowest)) && (ip[-1] == match[-1])) { ip--; match--; mLength++; } /* catch up */ offset_2 = offset_1; offset_1 = offset; + ZSTD_storeSeq(seqStorePtr, ip-anchor, anchor, offset + ZSTD_REP_MOVE, mLength-MINMATCH); } @@ -1983,7 +1998,6 @@ size_t ZSTD_compressBlock_doubleFast_generic(ZSTD_CCtx* cctx, ip++; ZSTD_storeSeq(seqStorePtr, ip-anchor, anchor, 0, mLength-MINMATCH); } else { - U32 offset; if ( (matchIndexL > lowestIndex) && (MEM_read64(matchLong) == MEM_read64(ip)) ) { mLength = ZSTD_count(ip+8, matchLong+8, iend) + 8; @@ -3405,7 +3419,7 @@ size_t ZSTD_compressBlock_ldm_generic(ZSTD_CCtx* cctx, /* Check immediate repcode */ while ( (ip < ilimit) - && ( (repToConfirm[1] > 0) + && ( (repToConfirm[1] > 0) && (repToConfirm[1] <= (U32)(ip-lowest)) && (MEM_read32(ip) == MEM_read32(ip - repToConfirm[1])) )) { size_t const rLength = ZSTD_count(ip+4, ip+4-repToConfirm[1], @@ -3413,7 +3427,7 @@ size_t ZSTD_compressBlock_ldm_generic(ZSTD_CCtx* cctx, /* Swap repToConfirm[1] <=> repToConfirm[0] */ { U32 const tmpOff = repToConfirm[1]; - repToConfirm[1] = repToConfirm[0]; + repToConfirm[1] = repToConfirm[0]; repToConfirm[0] = tmpOff; } @@ -3571,6 +3585,8 @@ static size_t ZSTD_compressBlock_ldm_extDict_generic( /* Call the block compressor on the remaining literals */ { + /* ip = current - backwardMatchLength + * The match is at (bestEntry->offset - backwardMatchLength) */ U32 const matchIndex = bestEntry->offset; U32 const offset = current - matchIndex; @@ -3687,7 +3703,7 @@ static size_t ZSTD_compressBlock_internal(ZSTD_CCtx* zc, void* dst, size_t dstCa size_t lastLLSize; const BYTE* anchor; const ZSTD_blockCompressor blockCompressor = - zc->ldmState.ldmEnable ? + zc->appliedParams.enableLdm? (zc->lowLimit < zc->dictLimit ? ZSTD_compressBlock_ldm_extDict : ZSTD_compressBlock_ldm) : ZSTD_selectBlockCompressor(zc->appliedParams.cParams.strategy, @@ -4870,7 +4886,6 @@ size_t ZSTD_compress_generic (ZSTD_CCtx* cctx, cctx->mtctx = ZSTDMT_createCCtx_advanced(params.nbThreads, cctx->customMem); if (cctx->mtctx == NULL) return ERROR(memory_allocation); } - DEBUGLOG(4, "call ZSTDMT_initCStream_internal as nbThreads=%u", params.nbThreads); CHECK_F( ZSTDMT_initCStream_internal( cctx->mtctx, diff --git a/lib/compress/zstdmt_compress.c b/lib/compress/zstdmt_compress.c index 166f99d7..ae4308b8 100644 --- a/lib/compress/zstdmt_compress.c +++ b/lib/compress/zstdmt_compress.c @@ -196,6 +196,8 @@ static ZSTD_CCtx_params ZSTDMT_makeJobCCtxParams(ZSTD_CCtx_params const params) jobParams.cParams = params.cParams; jobParams.fParams = params.fParams; jobParams.compressionLevel = params.compressionLevel; + + jobParams.enableLdm = params.enableLdm; return jobParams; } diff --git a/lib/zstd.h b/lib/zstd.h index c9fa34f3..fa2bbf06 100644 --- a/lib/zstd.h +++ b/lib/zstd.h @@ -978,9 +978,11 @@ typedef enum { /* advanced parameters - may not remain available after API update */ ZSTD_p_forceMaxWindow=1100, /* Force back-reference distances to remain < windowSize, * even when referencing into Dictionary content (default:0) */ - ZSTD_p_longDistanceMatching, /* Enable long distance matching. - * This increases the memory usage as well as the - * window size. */ + ZSTD_p_longDistanceMatching, /* Enable long distance matching. This + * increases the memory usage as well as the + * window size. Note: this should be set after + * ZSTD_p_compressionLevel and before + * ZSTD_p_windowLog. */ } ZSTD_cParameter; diff --git a/programs/bench.c b/programs/bench.c index a2c4efcf..5901205d 100644 --- a/programs/bench.c +++ b/programs/bench.c @@ -269,12 +269,12 @@ static int BMK_benchMem(const void* srcBuffer, size_t srcSize, #ifdef ZSTD_NEWAPI ZSTD_CCtx_setParameter(ctx, ZSTD_p_nbThreads, g_nbThreads); ZSTD_CCtx_setParameter(ctx, ZSTD_p_compressionLevel, cLevel); + ZSTD_CCtx_setParameter(ctx, ZSTD_p_longDistanceMatching, g_ldmFlag); ZSTD_CCtx_setParameter(ctx, ZSTD_p_windowLog, comprParams->windowLog); ZSTD_CCtx_setParameter(ctx, ZSTD_p_chainLog, comprParams->chainLog); ZSTD_CCtx_setParameter(ctx, ZSTD_p_searchLog, comprParams->searchLog); ZSTD_CCtx_setParameter(ctx, ZSTD_p_minMatch, comprParams->searchLength); ZSTD_CCtx_setParameter(ctx, ZSTD_p_targetLength, comprParams->targetLength); - ZSTD_CCtx_setParameter(ctx, ZSTD_p_longDistanceMatching, g_ldmFlag); ZSTD_CCtx_setParameter(ctx, ZSTD_p_compressionStrategy, comprParams->strategy); ZSTD_CCtx_loadDictionary(ctx, dictBuffer, dictBufferSize); #else diff --git a/programs/fileio.c b/programs/fileio.c index 8d024a9b..1d8fb22b 100644 --- a/programs/fileio.c +++ b/programs/fileio.c @@ -402,8 +402,11 @@ static cRess_t FIO_createCResources(const char* dictFileName, int cLevel, CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_p_dictIDFlag, g_dictIDFlag) ); CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_p_checksumFlag, g_checksumFlag) ); CHECK( ZSTD_CCtx_setPledgedSrcSize(ress.cctx, srcSize) ); - /* compression parameters */ + /* compression level */ CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_p_compressionLevel, cLevel) ); + /* long distance matching */ + CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_p_longDistanceMatching, g_ldmFlag) ); + /* compression parameters */ CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_p_windowLog, comprParams->windowLog) ); CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_p_chainLog, comprParams->chainLog) ); CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_p_hashLog, comprParams->hashLog) ); @@ -411,8 +414,6 @@ static cRess_t FIO_createCResources(const char* dictFileName, int cLevel, CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_p_minMatch, comprParams->searchLength) ); CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_p_targetLength, comprParams->targetLength) ); CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_p_compressionStrategy, (U32)comprParams->strategy) ); - /* long distance matching */ - CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_p_longDistanceMatching, g_ldmFlag) ); /* multi-threading */ CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_p_nbThreads, g_nbThreads) ); /* dictionary */ diff --git a/tests/fuzzer.c b/tests/fuzzer.c index ca67483d..1d11af2e 100644 --- a/tests/fuzzer.c +++ b/tests/fuzzer.c @@ -440,6 +440,8 @@ static int basicUnitTests(U32 seed, double compressibility) free(staticDCtxBuffer); } + + /* ZSTDMT simple MT compression test */ DISPLAYLEVEL(4, "test%3i : create ZSTDMT CCtx : ", testNb++); { ZSTDMT_CCtx* mtctx = ZSTDMT_createCCtx(2); @@ -1340,7 +1342,7 @@ static int fuzzerTests(U32 seed, U32 nbTests, unsigned startTest, U32 const maxD dictSize = FUZ_rLogLength(&lseed, dictLog); /* needed also for decompression */ dict = srcBuffer + (FUZ_rand(&lseed) % (srcBufferSize - dictSize)); - + CHECK_Z ( ZSTD_CCtx_setParameter(refCtx, ZSTD_p_longDistanceMatching, FUZ_rand(&lseed)&255) ); if (FUZ_rand(&lseed) & 0xF) { CHECK_Z ( ZSTD_compressBegin_usingDict(refCtx, dict, dictSize, cLevel) ); } else { @@ -1349,7 +1351,6 @@ static int fuzzerTests(U32 seed, U32 nbTests, unsigned startTest, U32 const maxD !(FUZ_rand(&lseed)&3) /* contentChecksumFlag*/, 0 /*NodictID*/ }; /* note : since dictionary is fake, dictIDflag has no impact */ ZSTD_parameters const p = FUZ_makeParams(cPar, fPar); - CHECK_Z ( ZSTD_compressBegin_advanced(refCtx, dict, dictSize, p, 0) ); } CHECK_Z( ZSTD_copyCCtx(ctx, refCtx, 0) ); diff --git a/tests/zstreamtest.c b/tests/zstreamtest.c index 3baa10ef..ba86fbcb 100644 --- a/tests/zstreamtest.c +++ b/tests/zstreamtest.c @@ -1380,7 +1380,7 @@ static int fuzzerTests_newAPI(U32 seed, U32 nbTests, unsigned startTest, double if (FUZ_rand(&lseed) & 1) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_minMatch, cParams.searchLength, useOpaqueAPI) ); if (FUZ_rand(&lseed) & 1) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_targetLength, cParams.targetLength, useOpaqueAPI) ); - if (FUZ_rand(&lseed) & 1) CHECK_Z( ZSTD_CCtx_setParameter(zc, ZSTD_p_longDistanceMatching, FUZ_rand(&lseed) & 63) ); + if (FUZ_rand(&lseed) & 1) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_longDistanceMatching, FUZ_rand(&lseed) & 63, useOpaqueAPI) ); /* unconditionally set, to be sync with decoder */ /* mess with frame parameters */ From 767a0b3be191475a2f65548679af6734670cc40c Mon Sep 17 00:00:00 2001 From: Stella Lau Date: Fri, 1 Sep 2017 12:24:59 -0700 Subject: [PATCH 03/15] Move ldm hashLog, bucketLog, and mml to cctxParams --- lib/common/zstd_internal.h | 18 +++- lib/compress/zstd_compress.c | 183 ++++++++++++++++++++++----------- lib/compress/zstdmt_compress.c | 2 +- lib/zstd.h | 11 +- tests/zstreamtest.c | 2 + 5 files changed, 148 insertions(+), 68 deletions(-) diff --git a/lib/common/zstd_internal.h b/lib/common/zstd_internal.h index d7404c06..b3d9a6c6 100644 --- a/lib/common/zstd_internal.h +++ b/lib/common/zstd_internal.h @@ -255,12 +255,19 @@ typedef struct { typedef struct { ldmEntry_t* hashTable; - BYTE* bucketOffsets; /* next position in bucket to insert entry */ - U32 hashLog; /* log size of hashTable */ - U32 bucketLog; /* log number of buckets, at most 4 */ - U32 hashEveryLog; /* log number of entries to skip */ + BYTE* bucketOffsets; /* Next position in bucket to insert entry */ + U32 hashEveryLog; /* Log number of entries to skip */ + U64 hashPower; /* Used to compute the rolling hash. + * Depends on ldmParams.minMatchLength */ } ldmState_t; +typedef struct { + U32 enableLdm; /* 1 if enable long distance matching */ + U32 hashLog; /* Log size of hashTable */ + U32 bucketLog; /* Log number of buckets, at most 4 */ + U32 minMatchLength; /* Minimum match length */ +} ldmParams_t; + typedef struct { U32 hufCTable[HUF_CTABLE_SIZE_U32(255)]; FSE_CTable offcodeCTable[FSE_CTABLE_SIZE_U32(OffFSELog, MaxOff)]; @@ -286,7 +293,8 @@ struct ZSTD_CCtx_params_s { unsigned jobSize; unsigned overlapSizeLog; - U32 enableLdm; /* 1 if enable long distance matching */ + /* Long distance matching parameters */ + ldmParams_t ldmParams; /* For use with createCCtxParams() and freeCCtxParams() only */ ZSTD_customMem customMem; diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index c02ee6cf..e74787f3 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -313,6 +313,16 @@ size_t ZSTDMT_CCtxParam_setMTCtxParameter( ZSTD_CCtx_params* params, ZSTDMT_parameter parameter, unsigned value); size_t ZSTDMT_initializeCCtxParameters(ZSTD_CCtx_params* params, unsigned nbThreads); +static size_t ZSTD_ldm_initializeParameters(ldmParams_t* params, U32 enableLdm) +{ + assert(LDM_BUCKET_SIZE_LOG <= LDM_BUCKET_SIZE_LOG_MAX); + params->enableLdm = enableLdm>0; + params->hashLog = LDM_HASH_LOG; + params->bucketLog = LDM_BUCKET_SIZE_LOG; + params->minMatchLength = LDM_MIN_MATCH_LENGTH; + return 0; +} + size_t ZSTD_CCtx_setParameter(ZSTD_CCtx* cctx, ZSTD_cParameter param, unsigned value) { if (cctx->streamStage != zcss_init) return ERROR(stage_wrong); @@ -369,6 +379,12 @@ size_t ZSTD_CCtx_setParameter(ZSTD_CCtx* cctx, ZSTD_cParameter param, unsigned v } return ZSTD_CCtxParam_setParameter(&cctx->requestedParams, param, value); + case ZSTD_p_ldmHashLog: + case ZSTD_p_ldmMinMatch: + if (value == 0) return 0; /* special value : 0 means "don't change anything" */ + if (cctx->cdict) return ERROR(stage_wrong); + return ZSTD_CCtxParam_setParameter(&cctx->requestedParams, param, value); + default: return ERROR(parameter_unsupported); } } @@ -469,11 +485,22 @@ size_t ZSTD_CCtxParam_setParameter( return ZSTDMT_CCtxParam_setMTCtxParameter(params, ZSTDMT_p_overlapSectionLog, value); case ZSTD_p_longDistanceMatching : - params->enableLdm = value>0; if (value != 0) { ZSTD_cLevelToCCtxParams(params); params->cParams.windowLog = LDM_WINDOW_LOG; } + return ZSTD_ldm_initializeParameters(¶ms->ldmParams, value); + + case ZSTD_p_ldmHashLog : + if (value == 0) return 0; + CLAMPCHECK(value, ZSTD_HASHLOG_MIN, ZSTD_HASHLOG_MAX); + params->ldmParams.hashLog = value; + return 0; + + case ZSTD_p_ldmMinMatch : + if (value == 0) return 0; + CLAMPCHECK(value, ZSTD_LDM_SEARCHLENGTH_MIN, ZSTD_LDM_SEARCHLENGTH_MAX); + params->ldmParams.minMatchLength = value; return 0; default: return ERROR(parameter_unsupported); @@ -512,7 +539,7 @@ size_t ZSTD_CCtx_setParametersUsingCCtxParams( } /* Copy long distance matching parameter */ - cctx->requestedParams.enableLdm = params->enableLdm; + cctx->requestedParams.ldmParams = params->ldmParams; /* customMem is used only for create/free params and can be ignored */ return 0; @@ -716,8 +743,9 @@ size_t ZSTD_estimateCCtxSize_advanced_usingCCtxParams(const ZSTD_CCtx_params* pa size_t const optSpace = ((cParams.strategy == ZSTD_btopt) || (cParams.strategy == ZSTD_btultra)) ? optBudget : 0; /* Ldm parameters can not currently be changed */ - size_t const ldmSpace = params->enableLdm ? - ZSTD_ldm_getTableSize(LDM_HASH_LOG, LDM_BUCKET_SIZE_LOG) : 0; + size_t const ldmSpace = params->ldmParams.enableLdm ? + ZSTD_ldm_getTableSize(params->ldmParams.hashLog, + params->ldmParams.bucketLog) : 0; size_t const neededSpace = entropySpace + tableSpace + tokenSpace + optSpace + ldmSpace; @@ -776,12 +804,24 @@ static U32 ZSTD_equivalentCParams(ZSTD_compressionParameters cParams1, & ((cParams1.searchLength==3) == (cParams2.searchLength==3)); /* hashlog3 space */ } +/** The parameters are equivalent if ldm is not enabled in both sets or + * all the parameters are equivalent. */ +static U32 ZSTD_equivalentLdmParams(ldmParams_t ldmParams1, + ldmParams_t ldmParams2) +{ + return (!ldmParams1.enableLdm && !ldmParams2.enableLdm) || + (ldmParams1.enableLdm == ldmParams2.enableLdm && + ldmParams1.hashLog == ldmParams2.hashLog && + ldmParams1.bucketLog == ldmParams2.bucketLog && + ldmParams1.minMatchLength == ldmParams2.minMatchLength); +} + /** Equivalence for resetCCtx purposes */ static U32 ZSTD_equivalentParams(ZSTD_CCtx_params params1, ZSTD_CCtx_params params2) { return ZSTD_equivalentCParams(params1.cParams, params2.cParams) && - params1.enableLdm == params2.enableLdm; + ZSTD_equivalentLdmParams(params1.ldmParams, params2.ldmParams); } /*! ZSTD_continueCCtx() : @@ -812,6 +852,8 @@ static size_t ZSTD_continueCCtx(ZSTD_CCtx* cctx, ZSTD_CCtx_params params, U64 pl typedef enum { ZSTDcrp_continue, ZSTDcrp_noMemset } ZSTD_compResetPolicy_e; typedef enum { ZSTDb_not_buffered, ZSTDb_buffered } ZSTD_buffered_policy_e; +static U64 ZSTD_ldm_getHashPower(U32 minMatchLength); + /*! ZSTD_resetCCtx_internal() : note : `params` are assumed fully validated at this stage */ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, @@ -831,13 +873,12 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, return ZSTD_continueCCtx(zc, params, pledgedSrcSize); } } - { - zc->ldmState.hashLog = LDM_HASH_LOG; - zc->ldmState.bucketLog = - MIN(LDM_BUCKET_SIZE_LOG, LDM_BUCKET_SIZE_LOG_MAX); + if (params.ldmParams.enableLdm) { zc->ldmState.hashEveryLog = - params.cParams.windowLog < zc->ldmState.hashLog ? - 0 : params.cParams.windowLog - zc->ldmState.hashLog; + params.cParams.windowLog < params.ldmParams.hashLog ? + 0 : params.cParams.windowLog - params.ldmParams.hashLog; + zc->ldmState.hashPower = + ZSTD_ldm_getHashPower(params.ldmParams.minMatchLength); } { size_t const blockSize = MIN(ZSTD_BLOCKSIZE_MAX, (size_t)1 << params.cParams.windowLog); @@ -855,7 +896,9 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, size_t const buffInSize = (zbuff==ZSTDb_buffered) ? ((size_t)1 << params.cParams.windowLog) + blockSize : 0; void* ptr; - size_t const ldmSpace = params.enableLdm ? ZSTD_ldm_getTableSize(zc->ldmState.hashLog, zc->ldmState.bucketLog) : 0; + size_t const ldmSpace = params.ldmParams.enableLdm ? + ZSTD_ldm_getTableSize(params.ldmParams.hashLog, + params.ldmParams.bucketLog) : 0; /* Check if workSpace is large enough, alloc a new one if needed */ { size_t const entropySpace = sizeof(ZSTD_entropyCTables_t); @@ -934,10 +977,10 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, } /* ldm space */ - if (params.enableLdm) { - size_t const ldmHSize = ((size_t)1) << zc->ldmState.hashLog; + if (params.ldmParams.enableLdm) { + size_t const ldmHSize = ((size_t)1) << params.ldmParams.hashLog; size_t const ldmBucketSize = - ((size_t)1) << (zc->ldmState.hashLog - zc->ldmState.bucketLog); + ((size_t)1) << (params.ldmParams.hashLog - params.ldmParams.bucketLog); memset(ptr, 0, ldmSpace); assert(((size_t)ptr & 3) == 0); /* ensure ptr is properly aligned */ zc->ldmState.hashTable = (ldmEntry_t*)ptr; @@ -1083,8 +1126,8 @@ static void ZSTD_reduceIndex (ZSTD_CCtx* zc, const U32 reducerValue) { U32 const h3Size = (zc->hashLog3) ? 1 << zc->hashLog3 : 0; ZSTD_reduceTable(zc->hashTable3, h3Size, reducerValue); } - { if (zc->appliedParams.enableLdm) { - U32 const ldmHSize = 1 << zc->ldmState.hashLog; + { if (zc->appliedParams.ldmParams.enableLdm) { + U32 const ldmHSize = 1 << zc->appliedParams.ldmParams.hashLog; ZSTD_ldm_reduceTable(zc->ldmState.hashTable, ldmHSize, reducerValue); } } @@ -3094,20 +3137,22 @@ static U32 ZSTD_ldm_getTag(U64 hash, U32 hbits, U32 numTagBits) /** ZSTD_ldm_getBucket() : * Returns a pointer to the start of the bucket associated with hash. */ -static ldmEntry_t* ZSTD_ldm_getBucket(ldmState_t* ldmState, size_t hash) +static ldmEntry_t* ZSTD_ldm_getBucket( + ldmState_t* ldmState, size_t hash, ldmParams_t const ldmParams) { - return ldmState->hashTable + (hash << ldmState->bucketLog); + return ldmState->hashTable + (hash << ldmParams.bucketLog); } /** ZSTD_ldm_insertEntry() : * Insert the entry with corresponding hash into the hash table */ static void ZSTD_ldm_insertEntry(ldmState_t* ldmState, - size_t const hash, const ldmEntry_t entry) + size_t const hash, const ldmEntry_t entry, + ldmParams_t const ldmParams) { BYTE* const bucketOffsets = ldmState->bucketOffsets; - *(ZSTD_ldm_getBucket(ldmState, hash) + bucketOffsets[hash]) = entry; + *(ZSTD_ldm_getBucket(ldmState, hash, ldmParams) + bucketOffsets[hash]) = entry; bucketOffsets[hash]++; - bucketOffsets[hash] &= (1 << ldmState->bucketLog) - 1; + bucketOffsets[hash] &= (1 << ldmParams.bucketLog) - 1; } /** ZSTD_ldm_makeEntryAndInsertByTag() : @@ -3122,7 +3167,8 @@ static void ZSTD_ldm_insertEntry(ldmState_t* ldmState, * by ldmState->hashEveryLog bits that make up the tag. */ static void ZSTD_ldm_makeEntryAndInsertByTag(ldmState_t* ldmState, U64 rollingHash, U32 hBits, - U32 const offset) + U32 const offset, + ldmParams_t const ldmParams) { U32 const tag = ZSTD_ldm_getTag(rollingHash, hBits, ldmState->hashEveryLog); U32 const tagMask = (1 << ldmState->hashEveryLog) - 1; @@ -3132,7 +3178,7 @@ static void ZSTD_ldm_makeEntryAndInsertByTag(ldmState_t* ldmState, ldmEntry_t entry; entry.offset = offset; entry.checksum = checksum; - ZSTD_ldm_insertEntry(ldmState, hash, entry); + ZSTD_ldm_insertEntry(ldmState, hash, entry, ldmParams); } } @@ -3170,14 +3216,15 @@ static U64 ZSTD_ldm_ipow(U64 base, U64 exp) return ret; } +static U64 ZSTD_ldm_getHashPower(U32 minMatchLength) { + assert(minMatchLength >= ZSTD_LDM_SEARCHLENGTH_MIN); + return ZSTD_ldm_ipow(prime8bytes, minMatchLength - 1); +} + /** ZSTD_ldm_updateHash() : - * Updates hash by removing toRemove and adding toAdd. - * - * Note: this currently relies on compiler optimization to avoid - * recalculating hashPower. */ -static U64 ZSTD_ldm_updateHash(U64 hash, BYTE toRemove, BYTE toAdd) + * Updates hash by removing toRemove and adding toAdd. */ +static U64 ZSTD_ldm_updateHash(U64 hash, BYTE toRemove, BYTE toAdd, U64 hashPower) { - U64 const hashPower = ZSTD_ldm_ipow(prime8bytes, LDM_MIN_MATCH_LENGTH - 1); hash -= ((toRemove + LDM_HASH_CHAR_OFFSET) * hashPower); hash *= prime8bytes; hash += toAdd + LDM_HASH_CHAR_OFFSET; @@ -3248,17 +3295,18 @@ static size_t ZSTD_ldm_fillFastTables(ZSTD_CCtx* zc, const void* end) static U64 ZSTD_ldm_fillLdmHashTable(ldmState_t* state, U64 lastHash, const BYTE* lastHashed, const BYTE* iend, const BYTE* base, - U32 hBits) + U32 hBits, ldmParams_t const ldmParams) { U64 rollingHash = lastHash; const BYTE* cur = lastHashed + 1; while (cur < iend) { rollingHash = ZSTD_ldm_updateHash(rollingHash, cur[-1], - cur[LDM_MIN_MATCH_LENGTH-1]); + cur[ldmParams.minMatchLength-1], + state->hashPower); ZSTD_ldm_makeEntryAndInsertByTag(state, rollingHash, hBits, - (U32)(cur - base)); + (U32)(cur - base), ldmParams); ++cur; } return rollingHash; @@ -3283,9 +3331,9 @@ static void ZSTD_ldm_limitTableUpdate(ZSTD_CCtx* cctx, const BYTE* anchor) * * This is a block compressor intended for long distance matching. * - * The function searches for matches of length at least LDM_MIN_MATCH_LENGTH - * using a hash table in cctx->ldmState. Matches can be at a distance of - * up to LDM_WINDOW_LOG. + * The function searches for matches of length at least + * ldmParams.minMatchLength using a hash table in cctx->ldmState. + * Matches can be at a distance of up to cParams.windowLog. * * Upon finding a match, the unmatched literals are compressed using a * ZSTD_blockCompressor (depending on the strategy in the compression @@ -3297,8 +3345,10 @@ size_t ZSTD_compressBlock_ldm_generic(ZSTD_CCtx* cctx, const void* src, size_t srcSize) { ldmState_t* const ldmState = &(cctx->ldmState); - const U32 hBits = ldmState->hashLog - ldmState->bucketLog; - const U32 ldmBucketSize = (1 << ldmState->bucketLog); + const ldmParams_t ldmParams = cctx->appliedParams.ldmParams; + const U64 hashPower = ldmState->hashPower; + const U32 hBits = ldmParams.hashLog - ldmParams.bucketLog; + const U32 ldmBucketSize = (1 << ldmParams.bucketLog); const U32 ldmTagMask = (1 << ldmState->hashEveryLog) - 1; seqStore_t* const seqStorePtr = &(cctx->seqStore); const BYTE* const base = cctx->base; @@ -3308,7 +3358,7 @@ size_t ZSTD_compressBlock_ldm_generic(ZSTD_CCtx* cctx, const U32 lowestIndex = cctx->dictLimit; const BYTE* const lowest = base + lowestIndex; const BYTE* const iend = istart + srcSize; - const BYTE* const ilimit = iend - LDM_MIN_MATCH_LENGTH; + const BYTE* const ilimit = iend - ldmParams.minMatchLength; const ZSTD_blockCompressor blockCompressor = ZSTD_selectBlockCompressor(cctx->appliedParams.cParams.strategy, 0); @@ -3330,9 +3380,10 @@ size_t ZSTD_compressBlock_ldm_generic(ZSTD_CCtx* cctx, ldmEntry_t* bestEntry = NULL; if (ip != istart) { rollingHash = ZSTD_ldm_updateHash(rollingHash, lastHashed[0], - lastHashed[LDM_MIN_MATCH_LENGTH]); + lastHashed[ldmParams.minMatchLength], + hashPower); } else { - rollingHash = ZSTD_ldm_getRollingHash(ip, LDM_MIN_MATCH_LENGTH); + rollingHash = ZSTD_ldm_getRollingHash(ip, ldmParams.minMatchLength); } lastHashed = ip; @@ -3347,7 +3398,8 @@ size_t ZSTD_compressBlock_ldm_generic(ZSTD_CCtx* cctx, { ldmEntry_t* const bucket = ZSTD_ldm_getBucket(ldmState, - ZSTD_ldm_getSmallHash(rollingHash, hBits)); + ZSTD_ldm_getSmallHash(rollingHash, hBits), + ldmParams); ldmEntry_t* cur; size_t bestMatchLength = 0; U32 const checksum = ZSTD_ldm_getChecksum(rollingHash, hBits); @@ -3361,7 +3413,7 @@ size_t ZSTD_compressBlock_ldm_generic(ZSTD_CCtx* cctx, } curForwardMatchLength = ZSTD_count(ip, pMatch, iend); - if (curForwardMatchLength < LDM_MIN_MATCH_LENGTH) { + if (curForwardMatchLength < ldmParams.minMatchLength) { continue; } curBackwardMatchLength = ZSTD_ldm_countBackwardsMatch( @@ -3381,7 +3433,8 @@ size_t ZSTD_compressBlock_ldm_generic(ZSTD_CCtx* cctx, /* No match found -- continue searching */ if (bestEntry == NULL) { ZSTD_ldm_makeEntryAndInsertByTag(ldmState, rollingHash, - hBits, current); + hBits, current, + ldmParams); ip++; continue; } @@ -3420,7 +3473,8 @@ size_t ZSTD_compressBlock_ldm_generic(ZSTD_CCtx* cctx, /* Insert the current entry into the hash table */ ZSTD_ldm_makeEntryAndInsertByTag(ldmState, rollingHash, hBits, - (U32)(lastHashed - base)); + (U32)(lastHashed - base), + ldmParams); assert(ip + backwardMatchLength == lastHashed); @@ -3429,12 +3483,11 @@ size_t ZSTD_compressBlock_ldm_generic(ZSTD_CCtx* cctx, if (ip + mLength < ilimit) { rollingHash = ZSTD_ldm_fillLdmHashTable( ldmState, rollingHash, lastHashed, - ip + mLength, base, hBits); + ip + mLength, base, hBits, ldmParams); lastHashed = ip + mLength - 1; } ip += mLength; anchor = ip; - /* Check immediate repcode */ while ( (ip < ilimit) && ( (repToConfirm[1] > 0) && (repToConfirm[1] <= (U32)(ip-lowest)) @@ -3455,7 +3508,7 @@ size_t ZSTD_compressBlock_ldm_generic(ZSTD_CCtx* cctx, if (ip + rLength < ilimit) { rollingHash = ZSTD_ldm_fillLdmHashTable( ldmState, rollingHash, lastHashed, - ip + rLength, base, hBits); + ip + rLength, base, hBits, ldmParams); lastHashed = ip + rLength - 1; } ip += rLength; @@ -3494,9 +3547,11 @@ static size_t ZSTD_compressBlock_ldm_extDict_generic( const void* src, size_t srcSize) { ldmState_t* ldmState = &(ctx->ldmState); - const U32 hBits = ldmState->hashLog - ldmState->bucketLog; - const U32 ldmBucketSize = (1 << ldmState->bucketLog); - const U32 ldmTagMask = (1 << ldmState->hashEveryLog) - 1; + const ldmParams_t ldmParams = ctx->appliedParams.ldmParams; + const U64 hashPower = ldmState->hashPower; + const U32 hBits = ldmParams.hashLog - ldmParams.bucketLog; + const U32 ldmBucketSize = (1 << ldmParams.bucketLog); + const U32 ldmTagMask = (1 << ctx->ldmState.hashEveryLog) - 1; seqStore_t* const seqStorePtr = &(ctx->seqStore); const BYTE* const base = ctx->base; const BYTE* const dictBase = ctx->dictBase; @@ -3509,7 +3564,7 @@ static size_t ZSTD_compressBlock_ldm_extDict_generic( const BYTE* const lowPrefixPtr = base + dictLimit; const BYTE* const dictEnd = dictBase + dictLimit; const BYTE* const iend = istart + srcSize; - const BYTE* const ilimit = iend - LDM_MIN_MATCH_LENGTH; + const BYTE* const ilimit = iend - ldmParams.minMatchLength; const ZSTD_blockCompressor blockCompressor = ZSTD_selectBlockCompressor(ctx->appliedParams.cParams.strategy, 1); @@ -3532,9 +3587,10 @@ static size_t ZSTD_compressBlock_ldm_extDict_generic( ldmEntry_t* bestEntry = NULL; if (ip != istart) { rollingHash = ZSTD_ldm_updateHash(rollingHash, lastHashed[0], - lastHashed[LDM_MIN_MATCH_LENGTH]); + lastHashed[ldmParams.minMatchLength], + hashPower); } else { - rollingHash = ZSTD_ldm_getRollingHash(ip, LDM_MIN_MATCH_LENGTH); + rollingHash = ZSTD_ldm_getRollingHash(ip, ldmParams.minMatchLength); } lastHashed = ip; @@ -3549,7 +3605,8 @@ static size_t ZSTD_compressBlock_ldm_extDict_generic( { ldmEntry_t* const bucket = ZSTD_ldm_getBucket(ldmState, - ZSTD_ldm_getSmallHash(rollingHash, hBits)); + ZSTD_ldm_getSmallHash(rollingHash, hBits), + ldmParams); ldmEntry_t* cur; size_t bestMatchLength = 0; U32 const checksum = ZSTD_ldm_getChecksum(rollingHash, hBits); @@ -3572,7 +3629,7 @@ static size_t ZSTD_compressBlock_ldm_extDict_generic( curForwardMatchLength = ZSTD_count_2segments( ip, pMatch, iend, matchEnd, lowPrefixPtr); - if (curForwardMatchLength < LDM_MIN_MATCH_LENGTH) { + if (curForwardMatchLength < ldmParams.minMatchLength) { continue; } curBackwardMatchLength = ZSTD_ldm_countBackwardsMatch( @@ -3592,7 +3649,8 @@ static size_t ZSTD_compressBlock_ldm_extDict_generic( /* No match found -- continue searching */ if (bestEntry == NULL) { ZSTD_ldm_makeEntryAndInsertByTag(ldmState, rollingHash, hBits, - (U32)(lastHashed - base)); + (U32)(lastHashed - base), + ldmParams); ip++; continue; } @@ -3632,14 +3690,16 @@ static size_t ZSTD_compressBlock_ldm_extDict_generic( /* Insert the current entry into the hash table */ ZSTD_ldm_makeEntryAndInsertByTag(ldmState, rollingHash, hBits, - (U32)(lastHashed - base)); + (U32)(lastHashed - base), + ldmParams); /* Fill the hash table from lastHashed+1 to ip+mLength */ assert(ip + backwardMatchLength == lastHashed); if (ip + mLength < ilimit) { rollingHash = ZSTD_ldm_fillLdmHashTable( ldmState, rollingHash, lastHashed, - ip + mLength, base, hBits); + ip + mLength, base, hBits, + ldmParams); lastHashed = ip + mLength - 1; } ip += mLength; @@ -3670,7 +3730,8 @@ static size_t ZSTD_compressBlock_ldm_extDict_generic( if (ip + repLength2 < ilimit) { rollingHash = ZSTD_ldm_fillLdmHashTable( ldmState, rollingHash, lastHashed, - ip + repLength2, base, hBits); + ip + repLength2, base, hBits, + ldmParams); lastHashed = ip + repLength2 - 1; } ip += repLength2; @@ -3721,7 +3782,7 @@ static size_t ZSTD_compressBlock_internal(ZSTD_CCtx* zc, void* dst, size_t dstCa size_t lastLLSize; const BYTE* anchor; const ZSTD_blockCompressor blockCompressor = - zc->appliedParams.enableLdm? + zc->appliedParams.ldmParams.enableLdm ? (zc->lowLimit < zc->dictLimit ? ZSTD_compressBlock_ldm_extDict : ZSTD_compressBlock_ldm) : ZSTD_selectBlockCompressor(zc->appliedParams.cParams.strategy, diff --git a/lib/compress/zstdmt_compress.c b/lib/compress/zstdmt_compress.c index f6266ade..ac451c35 100644 --- a/lib/compress/zstdmt_compress.c +++ b/lib/compress/zstdmt_compress.c @@ -197,7 +197,7 @@ static ZSTD_CCtx_params ZSTDMT_makeJobCCtxParams(ZSTD_CCtx_params const params) jobParams.fParams = params.fParams; jobParams.compressionLevel = params.compressionLevel; - jobParams.enableLdm = params.enableLdm; + jobParams.ldmParams = params.ldmParams; return jobParams; } diff --git a/lib/zstd.h b/lib/zstd.h index c4971040..bed583c9 100644 --- a/lib/zstd.h +++ b/lib/zstd.h @@ -390,6 +390,8 @@ ZSTDLIB_API size_t ZSTD_DStreamOutSize(void); /*!< recommended size for output #define ZSTD_SEARCHLENGTH_MIN 3 /* only for ZSTD_btopt, other strategies are limited to 4 */ #define ZSTD_TARGETLENGTH_MIN 4 #define ZSTD_TARGETLENGTH_MAX 999 +#define ZSTD_LDM_SEARCHLENGTH_MIN 4 +#define ZSTD_LDM_SEARCHLENGTH_MAX 4096 #define ZSTD_FRAMEHEADERSIZE_MAX 18 /* for static allocation */ #define ZSTD_FRAMEHEADERSIZE_MIN 6 @@ -980,7 +982,14 @@ typedef enum { * increases the memory usage as well as the * window size. Note: this should be set after * ZSTD_p_compressionLevel and before - * ZSTD_p_windowLog. */ + * ZSTD_p_windowLog and other LDM parameters. */ + ZSTD_p_ldmHashLog, /* Size of the table for long distance matching. + * Must be clamped between ZSTD_HASHLOG_MIN and + * ZSTD_HASHLOG_MAX */ + ZSTD_p_ldmMinMatch, /* Minimum size of searched matches for long distance matcher. + * Must be clamped between ZSTD_LDM_SEARCHLENGTH_MIN + * and ZSTD_LDM_SEARCHLENGTH_MAX. */ + } ZSTD_cParameter; diff --git a/tests/zstreamtest.c b/tests/zstreamtest.c index f248d260..dedb7eb3 100644 --- a/tests/zstreamtest.c +++ b/tests/zstreamtest.c @@ -1381,6 +1381,8 @@ static int fuzzerTests_newAPI(U32 seed, U32 nbTests, unsigned startTest, double if (FUZ_rand(&lseed) & 1) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_targetLength, cParams.targetLength, useOpaqueAPI) ); if (FUZ_rand(&lseed) & 1) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_longDistanceMatching, FUZ_rand(&lseed) & 63, useOpaqueAPI) ); + if (FUZ_rand(&lseed) & 7) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_ldmMinMatch, FUZ_rand(&lseed) % 128 + 4, useOpaqueAPI ) ); + if (FUZ_rand(&lseed) & 7) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_ldmHashLog, FUZ_rand(&lseed) % 18 + 10, useOpaqueAPI ) ); /* unconditionally set, to be sync with decoder */ /* mess with frame parameters */ From a1f04d518d34decb2ec5c6471d406fb8dad249ae Mon Sep 17 00:00:00 2001 From: Stella Lau Date: Fri, 1 Sep 2017 14:52:51 -0700 Subject: [PATCH 04/15] Move hashEveryLog to cctxParams and update cli --- lib/common/zstd_internal.h | 2 +- lib/compress/zstd_compress.c | 47 ++++++++++++++++++++++++------------ lib/zstd.h | 13 +++++++--- programs/bench.c | 22 +++++++++++++++++ programs/bench.h | 3 +++ programs/fileio.c | 19 +++++++++++++++ programs/fileio.h | 3 +++ programs/zstdcli.c | 16 ++++++++++++ tests/fuzzer.c | 1 - tests/zstreamtest.c | 2 -- 10 files changed, 105 insertions(+), 23 deletions(-) diff --git a/lib/common/zstd_internal.h b/lib/common/zstd_internal.h index b3d9a6c6..cd414646 100644 --- a/lib/common/zstd_internal.h +++ b/lib/common/zstd_internal.h @@ -256,7 +256,6 @@ typedef struct { typedef struct { ldmEntry_t* hashTable; BYTE* bucketOffsets; /* Next position in bucket to insert entry */ - U32 hashEveryLog; /* Log number of entries to skip */ U64 hashPower; /* Used to compute the rolling hash. * Depends on ldmParams.minMatchLength */ } ldmState_t; @@ -266,6 +265,7 @@ typedef struct { U32 hashLog; /* Log size of hashTable */ U32 bucketLog; /* Log number of buckets, at most 4 */ U32 minMatchLength; /* Minimum match length */ + U32 hashEveryLog; /* Log number of entries to skip */ } ldmParams_t; typedef struct { diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index e74787f3..fc8e9b0f 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -42,6 +42,7 @@ typedef enum { ZSTDcs_created=0, ZSTDcs_init, ZSTDcs_ongoing, ZSTDcs_ending } ZS #define LDM_WINDOW_LOG 27 #define LDM_HASH_LOG 20 #define LDM_HASH_CHAR_OFFSET 10 +#define LDM_HASHEVERYLOG_NOTSET 9999 /*-************************************* @@ -320,6 +321,7 @@ static size_t ZSTD_ldm_initializeParameters(ldmParams_t* params, U32 enableLdm) params->hashLog = LDM_HASH_LOG; params->bucketLog = LDM_BUCKET_SIZE_LOG; params->minMatchLength = LDM_MIN_MATCH_LENGTH; + params->hashEveryLog = LDM_HASHEVERYLOG_NOTSET; return 0; } @@ -385,6 +387,10 @@ size_t ZSTD_CCtx_setParameter(ZSTD_CCtx* cctx, ZSTD_cParameter param, unsigned v if (cctx->cdict) return ERROR(stage_wrong); return ZSTD_CCtxParam_setParameter(&cctx->requestedParams, param, value); + case ZSTD_p_ldmHashEveryLog: + if (cctx->cdict) return ERROR(stage_wrong); + return ZSTD_CCtxParam_setParameter(&cctx->requestedParams, param, value); + default: return ERROR(parameter_unsupported); } } @@ -503,6 +509,13 @@ size_t ZSTD_CCtxParam_setParameter( params->ldmParams.minMatchLength = value; return 0; + case ZSTD_p_ldmHashEveryLog : + if (value > ZSTD_WINDOWLOG_MAX - ZSTD_HASHLOG_MIN) { + return ERROR(parameter_outOfBound); + } + params->ldmParams.hashEveryLog = value; + return 0; + default: return ERROR(parameter_unsupported); } } @@ -538,7 +551,7 @@ size_t ZSTD_CCtx_setParametersUsingCCtxParams( cctx, ZSTD_p_overlapSizeLog, params->overlapSizeLog) ); } - /* Copy long distance matching parameter */ + /* Copy long distance matching parameters */ cctx->requestedParams.ldmParams = params->ldmParams; /* customMem is used only for create/free params and can be ignored */ @@ -742,7 +755,6 @@ size_t ZSTD_estimateCCtxSize_advanced_usingCCtxParams(const ZSTD_CCtx_params* pa + (ZSTD_OPT_NUM+1)*(sizeof(ZSTD_match_t) + sizeof(ZSTD_optimal_t)); size_t const optSpace = ((cParams.strategy == ZSTD_btopt) || (cParams.strategy == ZSTD_btultra)) ? optBudget : 0; - /* Ldm parameters can not currently be changed */ size_t const ldmSpace = params->ldmParams.enableLdm ? ZSTD_ldm_getTableSize(params->ldmParams.hashLog, params->ldmParams.bucketLog) : 0; @@ -813,7 +825,8 @@ static U32 ZSTD_equivalentLdmParams(ldmParams_t ldmParams1, (ldmParams1.enableLdm == ldmParams2.enableLdm && ldmParams1.hashLog == ldmParams2.hashLog && ldmParams1.bucketLog == ldmParams2.bucketLog && - ldmParams1.minMatchLength == ldmParams2.minMatchLength); + ldmParams1.minMatchLength == ldmParams2.minMatchLength && + ldmParams1.hashEveryLog == ldmParams2.hashEveryLog); } /** Equivalence for resetCCtx purposes */ @@ -866,6 +879,8 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, if (crp == ZSTDcrp_continue) { if (ZSTD_equivalentParams(params, zc->appliedParams)) { DEBUGLOG(5, "ZSTD_equivalentParams()==1"); + assert(!(params.ldmParams.enableLdm && + params.ldmParams.hashEveryLog == LDM_HASHEVERYLOG_NOTSET)); zc->entropy->hufCTable_repeatMode = HUF_repeat_none; zc->entropy->offcode_repeatMode = FSE_repeat_none; zc->entropy->matchlength_repeatMode = FSE_repeat_none; @@ -874,9 +889,11 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, } } if (params.ldmParams.enableLdm) { - zc->ldmState.hashEveryLog = - params.cParams.windowLog < params.ldmParams.hashLog ? - 0 : params.cParams.windowLog - params.ldmParams.hashLog; + if (params.ldmParams.hashEveryLog == LDM_HASHEVERYLOG_NOTSET) { + params.ldmParams.hashEveryLog = + params.cParams.windowLog < params.ldmParams.hashLog ? + 0 : params.cParams.windowLog - params.ldmParams.hashLog; + } zc->ldmState.hashPower = ZSTD_ldm_getHashPower(params.ldmParams.minMatchLength); } @@ -3159,19 +3176,19 @@ static void ZSTD_ldm_insertEntry(ldmState_t* ldmState, * * Gets the small hash, checksum, and tag from the rollingHash. * - * If the tag matches (1 << ldmState->hashEveryLog)-1, then + * If the tag matches (1 << ldmParams.hashEveryLog)-1, then * creates an ldmEntry from the offset, and inserts it into the hash table. * * hBits is the length of the small hash, which is the most significant hBits * of rollingHash. The checksum is the next 32 most significant bits, followed - * by ldmState->hashEveryLog bits that make up the tag. */ + * by ldmParams.hashEveryLog bits that make up the tag. */ static void ZSTD_ldm_makeEntryAndInsertByTag(ldmState_t* ldmState, U64 rollingHash, U32 hBits, U32 const offset, ldmParams_t const ldmParams) { - U32 const tag = ZSTD_ldm_getTag(rollingHash, hBits, ldmState->hashEveryLog); - U32 const tagMask = (1 << ldmState->hashEveryLog) - 1; + U32 const tag = ZSTD_ldm_getTag(rollingHash, hBits, ldmParams.hashEveryLog); + U32 const tagMask = (1 << ldmParams.hashEveryLog) - 1; if (tag == tagMask) { U32 const hash = ZSTD_ldm_getSmallHash(rollingHash, hBits); U32 const checksum = ZSTD_ldm_getChecksum(rollingHash, hBits); @@ -3349,7 +3366,7 @@ size_t ZSTD_compressBlock_ldm_generic(ZSTD_CCtx* cctx, const U64 hashPower = ldmState->hashPower; const U32 hBits = ldmParams.hashLog - ldmParams.bucketLog; const U32 ldmBucketSize = (1 << ldmParams.bucketLog); - const U32 ldmTagMask = (1 << ldmState->hashEveryLog) - 1; + const U32 ldmTagMask = (1 << ldmParams.hashEveryLog) - 1; seqStore_t* const seqStorePtr = &(cctx->seqStore); const BYTE* const base = cctx->base; const BYTE* const istart = (const BYTE*)src; @@ -3388,7 +3405,7 @@ size_t ZSTD_compressBlock_ldm_generic(ZSTD_CCtx* cctx, lastHashed = ip; /* Do not insert and do not look for a match */ - if (ZSTD_ldm_getTag(rollingHash, hBits, ldmState->hashEveryLog) != + if (ZSTD_ldm_getTag(rollingHash, hBits, ldmParams.hashEveryLog) != ldmTagMask) { ip++; continue; @@ -3546,12 +3563,12 @@ static size_t ZSTD_compressBlock_ldm_extDict_generic( ZSTD_CCtx* ctx, const void* src, size_t srcSize) { - ldmState_t* ldmState = &(ctx->ldmState); + ldmState_t* const ldmState = &(ctx->ldmState); const ldmParams_t ldmParams = ctx->appliedParams.ldmParams; const U64 hashPower = ldmState->hashPower; const U32 hBits = ldmParams.hashLog - ldmParams.bucketLog; const U32 ldmBucketSize = (1 << ldmParams.bucketLog); - const U32 ldmTagMask = (1 << ctx->ldmState.hashEveryLog) - 1; + const U32 ldmTagMask = (1 << ldmParams.hashEveryLog) - 1; seqStore_t* const seqStorePtr = &(ctx->seqStore); const BYTE* const base = ctx->base; const BYTE* const dictBase = ctx->dictBase; @@ -3594,7 +3611,7 @@ static size_t ZSTD_compressBlock_ldm_extDict_generic( } lastHashed = ip; - if (ZSTD_ldm_getTag(rollingHash, hBits, ldmState->hashEveryLog) != + if (ZSTD_ldm_getTag(rollingHash, hBits, ldmParams.hashEveryLog) != ldmTagMask) { /* Don't insert and don't look for a match */ ip++; diff --git a/lib/zstd.h b/lib/zstd.h index bed583c9..a7ad9771 100644 --- a/lib/zstd.h +++ b/lib/zstd.h @@ -984,11 +984,16 @@ typedef enum { * ZSTD_p_compressionLevel and before * ZSTD_p_windowLog and other LDM parameters. */ ZSTD_p_ldmHashLog, /* Size of the table for long distance matching. - * Must be clamped between ZSTD_HASHLOG_MIN and - * ZSTD_HASHLOG_MAX */ + * Must be clamped between ZSTD_HASHLOG_MIN and + * ZSTD_HASHLOG_MAX */ ZSTD_p_ldmMinMatch, /* Minimum size of searched matches for long distance matcher. - * Must be clamped between ZSTD_LDM_SEARCHLENGTH_MIN - * and ZSTD_LDM_SEARCHLENGTH_MAX. */ + * Must be clamped between ZSTD_LDM_SEARCHLENGTH_MIN + * and ZSTD_LDM_SEARCHLENGTH_MAX. */ + ZSTD_p_ldmHashEveryLog, /* Frequency of inserting/looking up entries in the + * LDM hash table. The default is + * (windowLog - ldmHashLog) to optimize hash table + * usage. Must be clamped between 0 and + * ZSTD_WINDOWLOG_MAX - ZSTD_HASHLOG_MIN. */ } ZSTD_cParameter; diff --git a/programs/bench.c b/programs/bench.c index d77f9a20..2a2510a2 100644 --- a/programs/bench.c +++ b/programs/bench.c @@ -134,6 +134,23 @@ void BMK_setLdmFlag(unsigned ldmFlag) { g_ldmFlag = ldmFlag; } +static U32 g_ldmMinMatch = 0; +void BMK_setLdmMinMatch(unsigned ldmMinMatch) { + g_ldmMinMatch = ldmMinMatch; +} + +static U32 g_ldmHashLog = 0; +void BMK_setLdmHashLog(unsigned ldmHashLog) { + g_ldmHashLog = ldmHashLog; +} + +#define BMK_LDM_HASHEVERYLOG_NOTSET 9999 +static U32 g_ldmHashEveryLog = BMK_LDM_HASHEVERYLOG_NOTSET; +void BMK_setLdmHashEveryLog(unsigned ldmHashEveryLog) { + g_ldmHashEveryLog = ldmHashEveryLog; +} + + /* ******************************************************** * Bench functions **********************************************************/ @@ -270,6 +287,11 @@ static int BMK_benchMem(const void* srcBuffer, size_t srcSize, ZSTD_CCtx_setParameter(ctx, ZSTD_p_nbThreads, g_nbThreads); ZSTD_CCtx_setParameter(ctx, ZSTD_p_compressionLevel, cLevel); ZSTD_CCtx_setParameter(ctx, ZSTD_p_longDistanceMatching, g_ldmFlag); + ZSTD_CCtx_setParameter(ctx, ZSTD_p_ldmMinMatch, g_ldmMinMatch); + ZSTD_CCtx_setParameter(ctx, ZSTD_p_ldmHashLog, g_ldmHashLog); + if (g_ldmHashEveryLog != BMK_LDM_HASHEVERYLOG_NOTSET) { + ZSTD_CCtx_setParameter(ctx, ZSTD_p_ldmHashEveryLog, g_ldmHashEveryLog); + } ZSTD_CCtx_setParameter(ctx, ZSTD_p_windowLog, comprParams->windowLog); ZSTD_CCtx_setParameter(ctx, ZSTD_p_chainLog, comprParams->chainLog); ZSTD_CCtx_setParameter(ctx, ZSTD_p_searchLog, comprParams->searchLog); diff --git a/programs/bench.h b/programs/bench.h index 7fb73c4d..04d220a9 100644 --- a/programs/bench.h +++ b/programs/bench.h @@ -26,5 +26,8 @@ void BMK_setNotificationLevel(unsigned level); void BMK_setAdditionalParam(int additionalParam); void BMK_setDecodeOnlyMode(unsigned decodeFlag); void BMK_setLdmFlag(unsigned ldmFlag); +void BMK_setLdmMinMatch(unsigned ldmMinMatch); +void BMK_setLdmHashLog(unsigned ldmHashLog); +void BMK_setLdmHashEveryLog(unsigned ldmHashEveryLog); #endif /* BENCH_H_121279284357 */ diff --git a/programs/fileio.c b/programs/fileio.c index c86b2533..fc390afe 100644 --- a/programs/fileio.c +++ b/programs/fileio.c @@ -217,6 +217,20 @@ static U32 g_ldmFlag = 0; void FIO_setLdmFlag(unsigned ldmFlag) { g_ldmFlag = (ldmFlag>0); } +static U32 g_ldmHashLog = 0; +void FIO_setLdmHashLog(unsigned ldmHashLog) { + g_ldmHashLog = ldmHashLog; +} +static U32 g_ldmMinMatch = 0; +void FIO_setLdmMinMatch(unsigned ldmMinMatch) { + g_ldmMinMatch = ldmMinMatch; +} +#define FIO_LDM_HASHEVERYLOG_NOTSET 9999 +static U32 g_ldmHashEveryLog = FIO_LDM_HASHEVERYLOG_NOTSET; +void FIO_setLdmHashEveryLog(unsigned ldmHashEveryLog) { + g_ldmHashEveryLog = ldmHashEveryLog; +} + /*-************************************* @@ -406,6 +420,11 @@ static cRess_t FIO_createCResources(const char* dictFileName, int cLevel, CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_p_compressionLevel, cLevel) ); /* long distance matching */ CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_p_longDistanceMatching, g_ldmFlag) ); + CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_p_ldmHashLog, g_ldmHashLog) ); + CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_p_ldmMinMatch, g_ldmMinMatch) ); + if (g_ldmHashEveryLog != FIO_LDM_HASHEVERYLOG_NOTSET) { + CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_p_ldmHashEveryLog, g_ldmHashEveryLog) ); + } /* compression parameters */ CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_p_windowLog, comprParams->windowLog) ); CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_p_chainLog, comprParams->chainLog) ); diff --git a/programs/fileio.h b/programs/fileio.h index 7e200b06..fabb46db 100644 --- a/programs/fileio.h +++ b/programs/fileio.h @@ -57,6 +57,9 @@ void FIO_setNbThreads(unsigned nbThreads); void FIO_setBlockSize(unsigned blockSize); void FIO_setOverlapLog(unsigned overlapLog); void FIO_setLdmFlag(unsigned ldmFlag); +void FIO_setLdmHashLog(unsigned ldmHashLog); +void FIO_setLdmMinMatch(unsigned ldmMinMatch); +void FIO_setLdmHashEveryLog(unsigned ldmHashEveryLog); /*-************************************* diff --git a/programs/zstdcli.c b/programs/zstdcli.c index b5fd1be5..78d6b339 100644 --- a/programs/zstdcli.c +++ b/programs/zstdcli.c @@ -72,7 +72,11 @@ static const unsigned g_defaultMaxDictSize = 110 KB; static const int g_defaultDictCLevel = 3; static const unsigned g_defaultSelectivityLevel = 9; #define OVERLAP_LOG_DEFAULT 9999 +#define LDM_HASHEVERYLOG_DEFAULT 9999 static U32 g_overlapLog = OVERLAP_LOG_DEFAULT; +static U32 g_ldmHashLog = 0; +static U32 g_ldmMinMatch = 0; +static U32 g_ldmHashEveryLog = LDM_HASHEVERYLOG_DEFAULT; /*-************************************ @@ -305,6 +309,9 @@ static unsigned parseCompressionParameters(const char* stringPtr, ZSTD_compressi if (longCommandWArg(&stringPtr, "targetLength=") || longCommandWArg(&stringPtr, "tlen=")) { params->targetLength = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; } if (longCommandWArg(&stringPtr, "strategy=") || longCommandWArg(&stringPtr, "strat=")) { params->strategy = (ZSTD_strategy)(readU32FromChar(&stringPtr)); if (stringPtr[0]==',') { stringPtr++; continue; } else break; } if (longCommandWArg(&stringPtr, "overlapLog=") || longCommandWArg(&stringPtr, "ovlog=")) { g_overlapLog = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; } + if (longCommandWArg(&stringPtr, "ldmHashLog=") || longCommandWArg(&stringPtr, "ldmHlog=")) { g_ldmHashLog = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; } + if (longCommandWArg(&stringPtr, "ldmSearchLength=") || longCommandWArg(&stringPtr, "ldmSlen=")) { g_ldmMinMatch = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; } + if (longCommandWArg(&stringPtr, "ldmHashEveryLog=")) { g_ldmHashEveryLog = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; } return 0; } @@ -724,6 +731,9 @@ int main(int argCount, const char* argv[]) BMK_setNbThreads(nbThreads); BMK_setNbSeconds(bench_nbSeconds); BMK_setLdmFlag(ldmFlag); + BMK_setLdmMinMatch(g_ldmMinMatch); + BMK_setLdmHashLog(g_ldmHashLog); + BMK_setLdmHashEveryLog(g_ldmHashEveryLog); BMK_benchFiles(filenameTable, filenameIdx, dictFileName, cLevel, cLevelLast, &compressionParams, setRealTimePrio); #endif (void)bench_nbSeconds; (void)blockSize; (void)setRealTimePrio; @@ -792,6 +802,12 @@ int main(int argCount, const char* argv[]) FIO_setNbThreads(nbThreads); FIO_setBlockSize((U32)blockSize); FIO_setLdmFlag(ldmFlag); + FIO_setLdmHashLog(g_ldmHashLog); + FIO_setLdmMinMatch(g_ldmMinMatch); + if (g_ldmHashEveryLog != LDM_HASHEVERYLOG_DEFAULT) { + FIO_setLdmHashEveryLog(g_ldmHashEveryLog); + } + if (g_overlapLog!=OVERLAP_LOG_DEFAULT) FIO_setOverlapLog(g_overlapLog); if ((filenameIdx==1) && outFileName) operationResult = FIO_compressFilename(outFileName, filenameTable[0], dictFileName, cLevel, &compressionParams); diff --git a/tests/fuzzer.c b/tests/fuzzer.c index 3b3e23b3..b2349870 100644 --- a/tests/fuzzer.c +++ b/tests/fuzzer.c @@ -1342,7 +1342,6 @@ static int fuzzerTests(U32 seed, U32 nbTests, unsigned startTest, U32 const maxD dictSize = FUZ_rLogLength(&lseed, dictLog); /* needed also for decompression */ dict = srcBuffer + (FUZ_rand(&lseed) % (srcBufferSize - dictSize)); - CHECK_Z ( ZSTD_CCtx_setParameter(refCtx, ZSTD_p_longDistanceMatching, FUZ_rand(&lseed)&255) ); if (FUZ_rand(&lseed) & 0xF) { CHECK_Z ( ZSTD_compressBegin_usingDict(refCtx, dict, dictSize, cLevel) ); } else { diff --git a/tests/zstreamtest.c b/tests/zstreamtest.c index dedb7eb3..f248d260 100644 --- a/tests/zstreamtest.c +++ b/tests/zstreamtest.c @@ -1381,8 +1381,6 @@ static int fuzzerTests_newAPI(U32 seed, U32 nbTests, unsigned startTest, double if (FUZ_rand(&lseed) & 1) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_targetLength, cParams.targetLength, useOpaqueAPI) ); if (FUZ_rand(&lseed) & 1) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_longDistanceMatching, FUZ_rand(&lseed) & 63, useOpaqueAPI) ); - if (FUZ_rand(&lseed) & 7) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_ldmMinMatch, FUZ_rand(&lseed) % 128 + 4, useOpaqueAPI ) ); - if (FUZ_rand(&lseed) & 7) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_ldmHashLog, FUZ_rand(&lseed) % 18 + 10, useOpaqueAPI ) ); /* unconditionally set, to be sync with decoder */ /* mess with frame parameters */ From 67d4a6161cc1237cf958d3abb4d8ce1a6623119c Mon Sep 17 00:00:00 2001 From: Stella Lau Date: Sat, 2 Sep 2017 21:10:36 -0700 Subject: [PATCH 05/15] Add ldmBucketSizeLog param --- lib/common/zstd_internal.h | 2 +- lib/compress/zstd_compress.c | 77 ++++++++++++++++++++++++++---------- lib/zstd.h | 28 +++++++------ programs/bench.c | 16 ++++++-- programs/bench.h | 1 + programs/fileio.c | 18 +++++++-- programs/fileio.h | 1 + programs/zstdcli.c | 18 +++++++-- tests/zstreamtest.c | 2 +- 9 files changed, 116 insertions(+), 47 deletions(-) diff --git a/lib/common/zstd_internal.h b/lib/common/zstd_internal.h index cd414646..2a270c3e 100644 --- a/lib/common/zstd_internal.h +++ b/lib/common/zstd_internal.h @@ -263,7 +263,7 @@ typedef struct { typedef struct { U32 enableLdm; /* 1 if enable long distance matching */ U32 hashLog; /* Log size of hashTable */ - U32 bucketLog; /* Log number of buckets, at most 4 */ + U32 bucketSizeLog; /* Log bucket size for collision resolution, at most 8 */ U32 minMatchLength; /* Minimum match length */ U32 hashEveryLog; /* Log number of entries to skip */ } ldmParams_t; diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index fc8e9b0f..d4d3ae96 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -37,7 +37,6 @@ static const U32 g_searchStrength = 8; /* control skip over incompressible dat typedef enum { ZSTDcs_created=0, ZSTDcs_init, ZSTDcs_ongoing, ZSTDcs_ending } ZSTD_compressionStage_e; #define LDM_BUCKET_SIZE_LOG 3 -#define LDM_BUCKET_SIZE_LOG_MAX 4 #define LDM_MIN_MATCH_LENGTH 64 #define LDM_WINDOW_LOG 27 #define LDM_HASH_LOG 20 @@ -202,6 +201,33 @@ size_t ZSTD_sizeof_CCtx(const ZSTD_CCtx* cctx) + cctx->outBuffSize + cctx->inBuffSize + ZSTDMT_sizeof_CCtx(cctx->mtctx); } +#if 0 +static void ZSTD_debugPrintCCtxParams(ZSTD_CCtx_params* params) +{ + DEBUGLOG(2, "======CCtxParams======"); + DEBUGLOG(2, "cParams: %u %u %u %u %u %u %u", + params->cParams.windowLog, + params->cParams.chainLog, + params->cParams.hashLog, + params->cParams.searchLog, + params->cParams.searchLength, + params->cParams.targetLength, + params->cParams.strategy); + DEBUGLOG(2, "fParams: %u %u %u", + params->fParams.contentSizeFlag, + params->fParams.checksumFlag, + params->fParams.noDictIDFlag); + DEBUGLOG(2, "cLevel, forceWindow: %u %u", + params->compressionLevel, + params->forceWindow); + DEBUGLOG(2, "ldm: %u %u %u %u %u", + params->ldmParams.enableLdm, + params->ldmParams.hashLog, + params->ldmParams.bucketSizeLog, + params->ldmParams.minMatchLength, + params->ldmParams.hashEveryLog); +} +#endif size_t ZSTD_sizeof_CStream(const ZSTD_CStream* zcs) { @@ -316,10 +342,10 @@ size_t ZSTDMT_initializeCCtxParameters(ZSTD_CCtx_params* params, unsigned nbThre static size_t ZSTD_ldm_initializeParameters(ldmParams_t* params, U32 enableLdm) { - assert(LDM_BUCKET_SIZE_LOG <= LDM_BUCKET_SIZE_LOG_MAX); + assert(LDM_BUCKET_SIZE_LOG <= ZSTD_LDM_BUCKETSIZELOG_MAX); params->enableLdm = enableLdm>0; params->hashLog = LDM_HASH_LOG; - params->bucketLog = LDM_BUCKET_SIZE_LOG; + params->bucketSizeLog = LDM_BUCKET_SIZE_LOG; params->minMatchLength = LDM_MIN_MATCH_LENGTH; params->hashEveryLog = LDM_HASHEVERYLOG_NOTSET; return 0; @@ -374,7 +400,7 @@ size_t ZSTD_CCtx_setParameter(ZSTD_CCtx* cctx, ZSTD_cParameter param, unsigned v DEBUGLOG(5, " setting overlap with nbThreads == %u", cctx->requestedParams.nbThreads); return ZSTD_CCtxParam_setParameter(&cctx->requestedParams, param, value); - case ZSTD_p_longDistanceMatching: + case ZSTD_p_enableLongDistanceMatching: if (cctx->cdict) return ERROR(stage_wrong); if (value != 0) { ZSTD_cLevelToCParams(cctx); @@ -387,6 +413,7 @@ size_t ZSTD_CCtx_setParameter(ZSTD_CCtx* cctx, ZSTD_cParameter param, unsigned v if (cctx->cdict) return ERROR(stage_wrong); return ZSTD_CCtxParam_setParameter(&cctx->requestedParams, param, value); + case ZSTD_p_ldmBucketSizeLog: case ZSTD_p_ldmHashEveryLog: if (cctx->cdict) return ERROR(stage_wrong); return ZSTD_CCtxParam_setParameter(&cctx->requestedParams, param, value); @@ -490,7 +517,7 @@ size_t ZSTD_CCtxParam_setParameter( if (params->nbThreads <= 1) return ERROR(parameter_unsupported); return ZSTDMT_CCtxParam_setMTCtxParameter(params, ZSTDMT_p_overlapSectionLog, value); - case ZSTD_p_longDistanceMatching : + case ZSTD_p_enableLongDistanceMatching : if (value != 0) { ZSTD_cLevelToCCtxParams(params); params->cParams.windowLog = LDM_WINDOW_LOG; @@ -509,6 +536,13 @@ size_t ZSTD_CCtxParam_setParameter( params->ldmParams.minMatchLength = value; return 0; + case ZSTD_p_ldmBucketSizeLog : + if (value > ZSTD_LDM_BUCKETSIZELOG_MAX) { + return ERROR(parameter_outOfBound); + } + params->ldmParams.bucketSizeLog = value; + return 0; + case ZSTD_p_ldmHashEveryLog : if (value > ZSTD_WINDOWLOG_MAX - ZSTD_HASHLOG_MIN) { return ERROR(parameter_outOfBound); @@ -722,12 +756,11 @@ ZSTD_compressionParameters ZSTD_adjustCParams(ZSTD_compressionParameters cPar, u } /* Estimate the space needed for long distance matching tables. */ -static size_t ZSTD_ldm_getTableSize(U32 ldmHashLog, U32 bucketLog) { - size_t const ldmHSize = ((size_t)1) << ldmHashLog; - size_t const ldmBucketLog = - MIN(bucketLog, LDM_BUCKET_SIZE_LOG_MAX); +static size_t ZSTD_ldm_getTableSize(U32 hashLog, U32 bucketSizeLog) { + size_t const ldmHSize = ((size_t)1) << hashLog; + size_t const ldmBucketSizeLog = MIN(bucketSizeLog, hashLog); size_t const ldmBucketSize = - ((size_t)1) << (ldmHashLog - ldmBucketLog); + ((size_t)1) << (hashLog - ldmBucketSizeLog); return ldmBucketSize + (ldmHSize * (sizeof(ldmEntry_t))); } @@ -757,7 +790,7 @@ size_t ZSTD_estimateCCtxSize_advanced_usingCCtxParams(const ZSTD_CCtx_params* pa size_t const ldmSpace = params->ldmParams.enableLdm ? ZSTD_ldm_getTableSize(params->ldmParams.hashLog, - params->ldmParams.bucketLog) : 0; + params->ldmParams.bucketSizeLog) : 0; size_t const neededSpace = entropySpace + tableSpace + tokenSpace + optSpace + ldmSpace; @@ -824,7 +857,7 @@ static U32 ZSTD_equivalentLdmParams(ldmParams_t ldmParams1, return (!ldmParams1.enableLdm && !ldmParams2.enableLdm) || (ldmParams1.enableLdm == ldmParams2.enableLdm && ldmParams1.hashLog == ldmParams2.hashLog && - ldmParams1.bucketLog == ldmParams2.bucketLog && + ldmParams1.bucketSizeLog == ldmParams2.bucketSizeLog && ldmParams1.minMatchLength == ldmParams2.minMatchLength && ldmParams1.hashEveryLog == ldmParams2.hashEveryLog); } @@ -889,11 +922,14 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, } } if (params.ldmParams.enableLdm) { + /* Adjust long distance matching parameters */ if (params.ldmParams.hashEveryLog == LDM_HASHEVERYLOG_NOTSET) { params.ldmParams.hashEveryLog = params.cParams.windowLog < params.ldmParams.hashLog ? 0 : params.cParams.windowLog - params.ldmParams.hashLog; } + params.ldmParams.bucketSizeLog = + MIN(params.ldmParams.bucketSizeLog, params.ldmParams.hashLog); zc->ldmState.hashPower = ZSTD_ldm_getHashPower(params.ldmParams.minMatchLength); } @@ -915,7 +951,7 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, size_t const ldmSpace = params.ldmParams.enableLdm ? ZSTD_ldm_getTableSize(params.ldmParams.hashLog, - params.ldmParams.bucketLog) : 0; + params.ldmParams.bucketSizeLog) : 0; /* Check if workSpace is large enough, alloc a new one if needed */ { size_t const entropySpace = sizeof(ZSTD_entropyCTables_t); @@ -997,7 +1033,7 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, if (params.ldmParams.enableLdm) { size_t const ldmHSize = ((size_t)1) << params.ldmParams.hashLog; size_t const ldmBucketSize = - ((size_t)1) << (params.ldmParams.hashLog - params.ldmParams.bucketLog); + ((size_t)1) << (params.ldmParams.hashLog - params.ldmParams.bucketSizeLog); memset(ptr, 0, ldmSpace); assert(((size_t)ptr & 3) == 0); /* ensure ptr is properly aligned */ zc->ldmState.hashTable = (ldmEntry_t*)ptr; @@ -3157,7 +3193,7 @@ static U32 ZSTD_ldm_getTag(U64 hash, U32 hbits, U32 numTagBits) static ldmEntry_t* ZSTD_ldm_getBucket( ldmState_t* ldmState, size_t hash, ldmParams_t const ldmParams) { - return ldmState->hashTable + (hash << ldmParams.bucketLog); + return ldmState->hashTable + (hash << ldmParams.bucketSizeLog); } /** ZSTD_ldm_insertEntry() : @@ -3169,7 +3205,7 @@ static void ZSTD_ldm_insertEntry(ldmState_t* ldmState, BYTE* const bucketOffsets = ldmState->bucketOffsets; *(ZSTD_ldm_getBucket(ldmState, hash, ldmParams) + bucketOffsets[hash]) = entry; bucketOffsets[hash]++; - bucketOffsets[hash] &= (1 << ldmParams.bucketLog) - 1; + bucketOffsets[hash] &= (1 << ldmParams.bucketSizeLog) - 1; } /** ZSTD_ldm_makeEntryAndInsertByTag() : @@ -3364,8 +3400,8 @@ size_t ZSTD_compressBlock_ldm_generic(ZSTD_CCtx* cctx, ldmState_t* const ldmState = &(cctx->ldmState); const ldmParams_t ldmParams = cctx->appliedParams.ldmParams; const U64 hashPower = ldmState->hashPower; - const U32 hBits = ldmParams.hashLog - ldmParams.bucketLog; - const U32 ldmBucketSize = (1 << ldmParams.bucketLog); + const U32 hBits = ldmParams.hashLog - ldmParams.bucketSizeLog; + const U32 ldmBucketSize = (1 << ldmParams.bucketSizeLog); const U32 ldmTagMask = (1 << ldmParams.hashEveryLog) - 1; seqStore_t* const seqStorePtr = &(cctx->seqStore); const BYTE* const base = cctx->base; @@ -3566,8 +3602,8 @@ static size_t ZSTD_compressBlock_ldm_extDict_generic( ldmState_t* const ldmState = &(ctx->ldmState); const ldmParams_t ldmParams = ctx->appliedParams.ldmParams; const U64 hashPower = ldmState->hashPower; - const U32 hBits = ldmParams.hashLog - ldmParams.bucketLog; - const U32 ldmBucketSize = (1 << ldmParams.bucketLog); + const U32 hBits = ldmParams.hashLog - ldmParams.bucketSizeLog; + const U32 ldmBucketSize = (1 << ldmParams.bucketSizeLog); const U32 ldmTagMask = (1 << ldmParams.hashEveryLog) - 1; seqStore_t* const seqStorePtr = &(ctx->seqStore); const BYTE* const base = ctx->base; @@ -5009,7 +5045,6 @@ size_t ZSTD_compress_generic (ZSTD_CCtx* cctx, return flushMin; } #endif - CHECK_F( ZSTD_compressStream_generic(cctx, output, input, endOp) ); DEBUGLOG(5, "completed ZSTD_compress_generic"); return cctx->outBuffContentSize - cctx->outBuffFlushedSize; /* remaining to flush */ diff --git a/lib/zstd.h b/lib/zstd.h index a7ad9771..e7d4fbdf 100644 --- a/lib/zstd.h +++ b/lib/zstd.h @@ -392,6 +392,7 @@ ZSTDLIB_API size_t ZSTD_DStreamOutSize(void); /*!< recommended size for output #define ZSTD_TARGETLENGTH_MAX 999 #define ZSTD_LDM_SEARCHLENGTH_MIN 4 #define ZSTD_LDM_SEARCHLENGTH_MAX 4096 +#define ZSTD_LDM_BUCKETSIZELOG_MAX 8 #define ZSTD_FRAMEHEADERSIZE_MAX 18 /* for static allocation */ #define ZSTD_FRAMEHEADERSIZE_MIN 6 @@ -978,22 +979,25 @@ typedef enum { /* advanced parameters - may not remain available after API update */ ZSTD_p_forceMaxWindow=1100, /* Force back-reference distances to remain < windowSize, * even when referencing into Dictionary content (default:0) */ - ZSTD_p_longDistanceMatching, /* Enable long distance matching. This - * increases the memory usage as well as the - * window size. Note: this should be set after - * ZSTD_p_compressionLevel and before - * ZSTD_p_windowLog and other LDM parameters. */ + ZSTD_p_enableLongDistanceMatching, /* Enable long distance matching. This increases the memory + * usage as well as window size. Note: setting this + * parameter resets all the LDM parameters as well as + * ZSTD_p_windowLog. It should be set after + * ZSTD_p_compressionLevel and before ZSTD_p_windowLog and + * other LDM parameters. Setting the compression level + * after this parameter overrides the window log, though LDM + * will remain enabled until explicitly disabled. */ ZSTD_p_ldmHashLog, /* Size of the table for long distance matching. - * Must be clamped between ZSTD_HASHLOG_MIN and - * ZSTD_HASHLOG_MAX */ + * Must be clamped between ZSTD_HASHLOG_MIN and ZSTD_HASHLOG_MAX. */ ZSTD_p_ldmMinMatch, /* Minimum size of searched matches for long distance matcher. * Must be clamped between ZSTD_LDM_SEARCHLENGTH_MIN * and ZSTD_LDM_SEARCHLENGTH_MAX. */ - ZSTD_p_ldmHashEveryLog, /* Frequency of inserting/looking up entries in the - * LDM hash table. The default is - * (windowLog - ldmHashLog) to optimize hash table - * usage. Must be clamped between 0 and - * ZSTD_WINDOWLOG_MAX - ZSTD_HASHLOG_MIN. */ + ZSTD_p_ldmBucketSizeLog, /* Log size of each bucket in the hash table for collision resolution. + * The maximum value is ZSTD_LDM_BUCKETSIZELOG_MAX. */ + ZSTD_p_ldmHashEveryLog, /* Frequency of inserting/looking up entries in the LDM hash table. + * The default is MAX(0, (windowLog - ldmHashLog)) to + * optimize hash table usage. + * Must be clamped between 0 and ZSTD_WINDOWLOG_MAX - ZSTD_HASHLOG_MIN. */ } ZSTD_cParameter; diff --git a/programs/bench.c b/programs/bench.c index 2a2510a2..68ac8c88 100644 --- a/programs/bench.c +++ b/programs/bench.c @@ -144,8 +144,13 @@ void BMK_setLdmHashLog(unsigned ldmHashLog) { g_ldmHashLog = ldmHashLog; } -#define BMK_LDM_HASHEVERYLOG_NOTSET 9999 -static U32 g_ldmHashEveryLog = BMK_LDM_HASHEVERYLOG_NOTSET; +#define BMK_LDM_PARAM_NOTSET 9999 +static U32 g_ldmBucketSizeLog = BMK_LDM_PARAM_NOTSET; +void BMK_setLdmBucketSizeLog(unsigned ldmBucketSizeLog) { + g_ldmBucketSizeLog = ldmBucketSizeLog; +} + +static U32 g_ldmHashEveryLog = BMK_LDM_PARAM_NOTSET; void BMK_setLdmHashEveryLog(unsigned ldmHashEveryLog) { g_ldmHashEveryLog = ldmHashEveryLog; } @@ -286,10 +291,13 @@ static int BMK_benchMem(const void* srcBuffer, size_t srcSize, #ifdef ZSTD_NEWAPI ZSTD_CCtx_setParameter(ctx, ZSTD_p_nbThreads, g_nbThreads); ZSTD_CCtx_setParameter(ctx, ZSTD_p_compressionLevel, cLevel); - ZSTD_CCtx_setParameter(ctx, ZSTD_p_longDistanceMatching, g_ldmFlag); + ZSTD_CCtx_setParameter(ctx, ZSTD_p_enableLongDistanceMatching, g_ldmFlag); ZSTD_CCtx_setParameter(ctx, ZSTD_p_ldmMinMatch, g_ldmMinMatch); ZSTD_CCtx_setParameter(ctx, ZSTD_p_ldmHashLog, g_ldmHashLog); - if (g_ldmHashEveryLog != BMK_LDM_HASHEVERYLOG_NOTSET) { + if (g_ldmBucketSizeLog != BMK_LDM_PARAM_NOTSET) { + ZSTD_CCtx_setParameter(ctx, ZSTD_p_ldmBucketSizeLog, g_ldmBucketSizeLog); + } + if (g_ldmHashEveryLog != BMK_LDM_PARAM_NOTSET) { ZSTD_CCtx_setParameter(ctx, ZSTD_p_ldmHashEveryLog, g_ldmHashEveryLog); } ZSTD_CCtx_setParameter(ctx, ZSTD_p_windowLog, comprParams->windowLog); diff --git a/programs/bench.h b/programs/bench.h index 04d220a9..6fd6c405 100644 --- a/programs/bench.h +++ b/programs/bench.h @@ -28,6 +28,7 @@ void BMK_setDecodeOnlyMode(unsigned decodeFlag); void BMK_setLdmFlag(unsigned ldmFlag); void BMK_setLdmMinMatch(unsigned ldmMinMatch); void BMK_setLdmHashLog(unsigned ldmHashLog); +void BMK_setLdmBucketSizeLog(unsigned ldmBucketSizeLog); void BMK_setLdmHashEveryLog(unsigned ldmHashEveryLog); #endif /* BENCH_H_121279284357 */ diff --git a/programs/fileio.c b/programs/fileio.c index fc390afe..7322328b 100644 --- a/programs/fileio.c +++ b/programs/fileio.c @@ -225,8 +225,14 @@ static U32 g_ldmMinMatch = 0; void FIO_setLdmMinMatch(unsigned ldmMinMatch) { g_ldmMinMatch = ldmMinMatch; } -#define FIO_LDM_HASHEVERYLOG_NOTSET 9999 -static U32 g_ldmHashEveryLog = FIO_LDM_HASHEVERYLOG_NOTSET; + +#define FIO_LDM_PARAM_NOTSET 9999 +static U32 g_ldmBucketSizeLog = FIO_LDM_PARAM_NOTSET; +void FIO_setLdmBucketSizeLog(unsigned ldmBucketSizeLog) { + g_ldmBucketSizeLog = ldmBucketSizeLog; +} + +static U32 g_ldmHashEveryLog = FIO_LDM_PARAM_NOTSET; void FIO_setLdmHashEveryLog(unsigned ldmHashEveryLog) { g_ldmHashEveryLog = ldmHashEveryLog; } @@ -419,10 +425,14 @@ static cRess_t FIO_createCResources(const char* dictFileName, int cLevel, /* compression level */ CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_p_compressionLevel, cLevel) ); /* long distance matching */ - CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_p_longDistanceMatching, g_ldmFlag) ); + CHECK( ZSTD_CCtx_setParameter( + ress.cctx, ZSTD_p_enableLongDistanceMatching, g_ldmFlag) ); CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_p_ldmHashLog, g_ldmHashLog) ); CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_p_ldmMinMatch, g_ldmMinMatch) ); - if (g_ldmHashEveryLog != FIO_LDM_HASHEVERYLOG_NOTSET) { + if (g_ldmBucketSizeLog != FIO_LDM_PARAM_NOTSET) { + CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_p_ldmBucketSizeLog, g_ldmBucketSizeLog) ); + } + if (g_ldmHashEveryLog != FIO_LDM_PARAM_NOTSET) { CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_p_ldmHashEveryLog, g_ldmHashEveryLog) ); } /* compression parameters */ diff --git a/programs/fileio.h b/programs/fileio.h index fabb46db..20ee2ebc 100644 --- a/programs/fileio.h +++ b/programs/fileio.h @@ -59,6 +59,7 @@ void FIO_setOverlapLog(unsigned overlapLog); void FIO_setLdmFlag(unsigned ldmFlag); void FIO_setLdmHashLog(unsigned ldmHashLog); void FIO_setLdmMinMatch(unsigned ldmMinMatch); +void FIO_setLdmBucketSizeLog(unsigned ldmBucketSizeLog); void FIO_setLdmHashEveryLog(unsigned ldmHashEveryLog); diff --git a/programs/zstdcli.c b/programs/zstdcli.c index 78d6b339..a60537a0 100644 --- a/programs/zstdcli.c +++ b/programs/zstdcli.c @@ -72,11 +72,12 @@ static const unsigned g_defaultMaxDictSize = 110 KB; static const int g_defaultDictCLevel = 3; static const unsigned g_defaultSelectivityLevel = 9; #define OVERLAP_LOG_DEFAULT 9999 -#define LDM_HASHEVERYLOG_DEFAULT 9999 +#define LDM_PARAM_DEFAULT 9999 /* Default for parameters where 0 is valid */ static U32 g_overlapLog = OVERLAP_LOG_DEFAULT; static U32 g_ldmHashLog = 0; static U32 g_ldmMinMatch = 0; -static U32 g_ldmHashEveryLog = LDM_HASHEVERYLOG_DEFAULT; +static U32 g_ldmHashEveryLog = LDM_PARAM_DEFAULT; +static U32 g_ldmBucketSizeLog = LDM_PARAM_DEFAULT; /*-************************************ @@ -311,6 +312,7 @@ static unsigned parseCompressionParameters(const char* stringPtr, ZSTD_compressi if (longCommandWArg(&stringPtr, "overlapLog=") || longCommandWArg(&stringPtr, "ovlog=")) { g_overlapLog = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; } if (longCommandWArg(&stringPtr, "ldmHashLog=") || longCommandWArg(&stringPtr, "ldmHlog=")) { g_ldmHashLog = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; } if (longCommandWArg(&stringPtr, "ldmSearchLength=") || longCommandWArg(&stringPtr, "ldmSlen=")) { g_ldmMinMatch = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; } + if (longCommandWArg(&stringPtr, "ldmBucketSizeLog=")) { g_ldmBucketSizeLog = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; } if (longCommandWArg(&stringPtr, "ldmHashEveryLog=")) { g_ldmHashEveryLog = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; } return 0; } @@ -733,7 +735,12 @@ int main(int argCount, const char* argv[]) BMK_setLdmFlag(ldmFlag); BMK_setLdmMinMatch(g_ldmMinMatch); BMK_setLdmHashLog(g_ldmHashLog); - BMK_setLdmHashEveryLog(g_ldmHashEveryLog); + if (g_ldmBucketSizeLog != LDM_PARAM_DEFAULT) { + BMK_setLdmBucketSizeLog(g_ldmBucketSizeLog); + } + if (g_ldmHashEveryLog != LDM_PARAM_DEFAULT) { + BMK_setLdmHashEveryLog(g_ldmHashEveryLog); + } BMK_benchFiles(filenameTable, filenameIdx, dictFileName, cLevel, cLevelLast, &compressionParams, setRealTimePrio); #endif (void)bench_nbSeconds; (void)blockSize; (void)setRealTimePrio; @@ -804,7 +811,10 @@ int main(int argCount, const char* argv[]) FIO_setLdmFlag(ldmFlag); FIO_setLdmHashLog(g_ldmHashLog); FIO_setLdmMinMatch(g_ldmMinMatch); - if (g_ldmHashEveryLog != LDM_HASHEVERYLOG_DEFAULT) { + if (g_ldmBucketSizeLog != LDM_PARAM_DEFAULT) { + FIO_setLdmBucketSizeLog(g_ldmBucketSizeLog); + } + if (g_ldmHashEveryLog != LDM_PARAM_DEFAULT) { FIO_setLdmHashEveryLog(g_ldmHashEveryLog); } diff --git a/tests/zstreamtest.c b/tests/zstreamtest.c index f248d260..76495d45 100644 --- a/tests/zstreamtest.c +++ b/tests/zstreamtest.c @@ -1380,7 +1380,7 @@ static int fuzzerTests_newAPI(U32 seed, U32 nbTests, unsigned startTest, double if (FUZ_rand(&lseed) & 1) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_minMatch, cParams.searchLength, useOpaqueAPI) ); if (FUZ_rand(&lseed) & 1) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_targetLength, cParams.targetLength, useOpaqueAPI) ); - if (FUZ_rand(&lseed) & 1) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_longDistanceMatching, FUZ_rand(&lseed) & 63, useOpaqueAPI) ); + if (FUZ_rand(&lseed) & 1) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_enableLongDistanceMatching, FUZ_rand(&lseed) & 63, useOpaqueAPI) ); /* unconditionally set, to be sync with decoder */ /* mess with frame parameters */ From 643d28c7015635a89703a66425634fff796efe81 Mon Sep 17 00:00:00 2001 From: Stella Lau Date: Tue, 5 Sep 2017 11:05:57 -0700 Subject: [PATCH 06/15] Add ldm options to 'man zstd' --- lib/zstd.h | 28 +++++++++++++++------- programs/zstd.1 | 58 +++++++++++++++++++++++++++++++++++++++++++++- programs/zstd.1.md | 47 +++++++++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 10 deletions(-) diff --git a/lib/zstd.h b/lib/zstd.h index e7d4fbdf..4879c4a6 100644 --- a/lib/zstd.h +++ b/lib/zstd.h @@ -979,24 +979,34 @@ typedef enum { /* advanced parameters - may not remain available after API update */ ZSTD_p_forceMaxWindow=1100, /* Force back-reference distances to remain < windowSize, * even when referencing into Dictionary content (default:0) */ - ZSTD_p_enableLongDistanceMatching, /* Enable long distance matching. This increases the memory - * usage as well as window size. Note: setting this - * parameter resets all the LDM parameters as well as - * ZSTD_p_windowLog. It should be set after + ZSTD_p_enableLongDistanceMatching, /* Enable long distance matching. + * This parameter is designed to improve the compression + * ratio for large inputs with long distance matches. + * This increases the memory usage as well as window size. + * Note: setting this parameter sets all the LDM parameters + * as well as ZSTD_p_windowLog. It should be set after * ZSTD_p_compressionLevel and before ZSTD_p_windowLog and * other LDM parameters. Setting the compression level * after this parameter overrides the window log, though LDM * will remain enabled until explicitly disabled. */ - ZSTD_p_ldmHashLog, /* Size of the table for long distance matching. - * Must be clamped between ZSTD_HASHLOG_MIN and ZSTD_HASHLOG_MAX. */ + ZSTD_p_ldmHashLog, /* Size of the table for long distance matching, as a power of 2. + * Larger values increase memory usage and compression ratio, but decrease + * compression speed. + * Must be clamped between ZSTD_HASHLOG_MIN and ZSTD_HASHLOG_MAX + * (default: 20). */ ZSTD_p_ldmMinMatch, /* Minimum size of searched matches for long distance matcher. + * Larger/too small values usually decrease compression ratio. * Must be clamped between ZSTD_LDM_SEARCHLENGTH_MIN - * and ZSTD_LDM_SEARCHLENGTH_MAX. */ - ZSTD_p_ldmBucketSizeLog, /* Log size of each bucket in the hash table for collision resolution. - * The maximum value is ZSTD_LDM_BUCKETSIZELOG_MAX. */ + * and ZSTD_LDM_SEARCHLENGTH_MAX (default: 64). */ + ZSTD_p_ldmBucketSizeLog, /* Log size of each bucket in the LDM hash table for collision resolution. + * Larger values usually improve collision resolution but may decrease + * compression speed. + * The maximum value is ZSTD_LDM_BUCKETSIZELOG_MAX (default: 3). */ ZSTD_p_ldmHashEveryLog, /* Frequency of inserting/looking up entries in the LDM hash table. * The default is MAX(0, (windowLog - ldmHashLog)) to * optimize hash table usage. + * Larger values improve compression speed. Deviating far from the + * default value will likely result in a decrease in compression ratio. * Must be clamped between 0 and ZSTD_WINDOWLOG_MAX - ZSTD_HASHLOG_MIN. */ } ZSTD_cParameter; diff --git a/programs/zstd.1 b/programs/zstd.1 index 5a91eea2..13c804ae 100644 --- a/programs/zstd.1 +++ b/programs/zstd.1 @@ -1,5 +1,5 @@ . -.TH "ZSTD" "1" "August 2017" "zstd 1.3.1" "User Commands" +.TH "ZSTD" "1" "September 2017" "zstd 1.3.1" "User Commands" . .SH "NAME" \fBzstd\fR \- zstd, zstdmt, unzstd, zstdcat \- Compress or decompress \.zst files @@ -104,6 +104,10 @@ Display information related to a zstd compressed file, such as size, ratio, and unlocks high compression levels 20+ (maximum 22), using a lot more memory\. Note that decompression will also require more memory when using these levels\. . .TP +\fB\-\-long\fR +enables long distance matching\. This increases the window size (\fBwindowLog\fR) and memory usage for both the compressor and decompressor\. This setting is designed to improve the compression ratio for files with long matches at a large distance (up to the maximum window size, 128 MiB)\. +. +.TP \fB\-T#\fR, \fB\-\-threads=#\fR Compress using \fB#\fR threads (default: 1)\. If \fB#\fR is 0, attempt to detect and use the number of physical CPU cores\. In all cases, the nb of threads is capped to ZSTDMT_NBTHREADS_MAX==256\. This modifier does nothing if \fBzstd\fR is compiled without multithread support\. . @@ -322,6 +326,58 @@ Determine \fBoverlapSize\fR, amount of data reloaded from previous job\. This pa .IP The minimum \fIovlog\fR is 0, and the maximum is 9\. 0 means "no overlap", hence completely independent jobs\. 9 means "full overlap", meaning up to \fBwindowSize\fR is reloaded from previous job\. Reducing \fIovlog\fR by 1 reduces the amount of reload by a factor 2\. Default \fIovlog\fR is 6, which means "reload \fBwindowSize / 8\fR"\. Exception : the maximum compression level (22) has a default \fIovlog\fR of 9\. . +.TP +\fBldmHashLog\fR=\fIldmHlog\fR, \fBldmHlog\fR=\fIldmHlog\fR +Specify the maximum size for a hash table used for long distance matching\. +. +.IP +This option is ignored unless long distance matching is enabled\. +. +.IP +Bigger hash tables usually improve compression ratio at the expense of more memory during compression and a decrease in compression speed\. +. +.IP +The minimum \fIldmHlog\fR is 6 and the maximum is 26 (default: 20)\. +. +.TP +\fBldmSearchLength\fR=\fIldmSlen\fR, \fBldmSlen\fR=\fIldmSlen\fR +Specify the minimum searched length of a match for long distance matching\. +. +.IP +This option is ignored unless long distance matching is enabled\. +. +.IP +Larger/very small values usually decrease compression ratio\. +. +.IP +The minumum \fIldmSlen\fR is 4 and the maximum is 4096 (default: 64)\. +. +.TP +\fBldmBucketSizeLog\fR=\fIldmBucketSizeLog\fR +Specify the size of each bucket for the hash table used for long distance matching\. +. +.IP +This option is ignored unless long distance matching is enabled\. +. +.IP +Larger bucket sizes improve collision resolution but decrease compression speed\. +. +.IP +The minimum \fIldmBucketSizeLog\fR is 0 and the maximum is 8 (default: 3)\. +. +.TP +\fBldmHashEveryLog\fR=\fIldmHashEveryLog\fR +Specify the frequency of inserting entries into the long distance matching hash table\. +. +.IP +This option is ignored unless long distance matching is enabled\. +. +.IP +Larger values will improve compression speed\. Deviating far from the default value will likely result in a decrease in compression ratio\. +. +.IP +The default value is \fBwLog \- ldmHlog\fR\. +. .SS "\-B#:" Select the size of each compression job\. This parameter is available only when multi\-threading is enabled\. Default value is \fB4 * windowSize\fR, which means it varies depending on compression level\. \fB\-B#\fR makes it possible to select a custom value\. Note that job size must respect a minimum value which is enforced transparently\. This minimum is either 1 MB, or \fBoverlapSize\fR, whichever is largest\. . diff --git a/programs/zstd.1.md b/programs/zstd.1.md index 4310afa1..5f6aa450 100644 --- a/programs/zstd.1.md +++ b/programs/zstd.1.md @@ -105,6 +105,12 @@ the last one takes effect. * `--ultra`: unlocks high compression levels 20+ (maximum 22), using a lot more memory. Note that decompression will also require more memory when using these levels. +* `--long`: + enables long distance matching. + This increases the window size (`windowLog`) and memory usage for both the + compressor and decompressor. This setting is designed to improve the + compression ratio for files with long matches at a large distance + (up to the maximum window size, 128 MiB). * `-T#`, `--threads=#`: Compress using `#` threads (default: 1). If `#` is 0, attempt to detect and use the number of physical CPU cores. @@ -327,6 +333,47 @@ The list of available _options_: Default _ovlog_ is 6, which means "reload `windowSize / 8`". Exception : the maximum compression level (22) has a default _ovlog_ of 9. +- `ldmHashLog`=_ldmHlog_, `ldmHlog`=_ldmHlog_: + Specify the maximum size for a hash table used for long distance matching. + + This option is ignored unless long distance matching is enabled. + + Bigger hash tables usually improve compression ratio at the expense of more + memory during compression and a decrease in compression speed. + + The minimum _ldmHlog_ is 6 and the maximum is 26 (default: 20). + +- `ldmSearchLength`=_ldmSlen_, `ldmSlen`=_ldmSlen_: + Specify the minimum searched length of a match for long distance matching. + + This option is ignored unless long distance matching is enabled. + + Larger/very small values usually decrease compression ratio. + + The minumum _ldmSlen_ is 4 and the maximum is 4096 (default: 64). + +- `ldmBucketSizeLog`=_ldmBucketSizeLog_: + Specify the size of each bucket for the hash table used for long distance + matching. + + This option is ignored unless long distance matching is enabled. + + Larger bucket sizes improve collision resolution but decrease compression + speed. + + The minimum _ldmBucketSizeLog_ is 0 and the maximum is 8 (default: 3). + +- `ldmHashEveryLog`=_ldmHashEveryLog_: + Specify the frequency of inserting entries into the long distance matching + hash table. + + This option is ignored unless long distance matching is enabled. + + Larger values will improve compression speed. Deviating far from the + default value will likely result in a decrease in compression ratio. + + The default value is `wLog - ldmHlog`. + ### -B#: Select the size of each compression job. This parameter is available only when multi-threading is enabled. From 08d33fe1c91cb2f6a4f8316c2df3731e904df2c4 Mon Sep 17 00:00:00 2001 From: Stella Lau Date: Tue, 5 Sep 2017 15:50:14 -0700 Subject: [PATCH 07/15] Fix parameter handling in copyCCtx with cdict --- lib/compress/zstd_compress.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index d4d3ae96..7863fae0 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -1094,7 +1094,8 @@ static size_t ZSTD_copyCCtx_internal(ZSTD_CCtx* dstCCtx, if (srcCCtx->stage!=ZSTDcs_init) return ERROR(stage_wrong); memcpy(&dstCCtx->customMem, &srcCCtx->customMem, sizeof(ZSTD_customMem)); - { ZSTD_CCtx_params params = srcCCtx->appliedParams; + { ZSTD_CCtx_params params = dstCCtx->requestedParams; + params.cParams = srcCCtx->appliedParams.cParams; params.fParams = fParams; ZSTD_resetCCtx_internal(dstCCtx, params, pledgedSrcSize, ZSTDcrp_noMemset, zbuff); From 98b85426f1e777b6e607dc1281e292dc00a2f6e5 Mon Sep 17 00:00:00 2001 From: Stella Lau Date: Tue, 5 Sep 2017 20:41:37 -0700 Subject: [PATCH 08/15] Fix setting of nextToUpdate at end of ldm matcher --- lib/compress/zstd_compress.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 7863fae0..bbca6729 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -1030,6 +1030,7 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, } /* ldm space */ + /* TODO */ if (params.ldmParams.enableLdm) { size_t const ldmHSize = ((size_t)1) << params.ldmParams.hashLog; size_t const ldmBucketSize = @@ -3580,7 +3581,7 @@ size_t ZSTD_compressBlock_ldm_generic(ZSTD_CCtx* cctx, ZSTD_ldm_fillFastTables(cctx, anchor); lastLiterals = blockCompressor(cctx, anchor, iend - anchor); - cctx->nextToUpdate = (U32)(ip - base); + cctx->nextToUpdate = (U32)(iend - base); /* Restore seqStorePtr->rep */ for (i = 0; i < ZSTD_REP_NUM; i++) @@ -3805,7 +3806,7 @@ static size_t ZSTD_compressBlock_ldm_extDict_generic( /* Call the block compressor one last time on the last literals */ lastLiterals = blockCompressor(ctx, anchor, iend - anchor); - ctx->nextToUpdate = (U32)(ip - base); + ctx->nextToUpdate = (U32)(iend - base); /* Restore seqStorePtr->rep */ for (i = 0; i < ZSTD_REP_NUM; i++) From c706de53959eb9e245183f5c39220245600c7ada Mon Sep 17 00:00:00 2001 From: Stella Lau Date: Tue, 5 Sep 2017 21:11:18 -0700 Subject: [PATCH 09/15] Rename and add short ldm parameters in cli --- lib/compress/zstd_compress.c | 13 +++++-------- lib/zstd.h | 2 +- programs/zstd.1 | 16 ++++++++-------- programs/zstd.1.md | 18 +++++++++--------- programs/zstdcli.c | 8 ++++---- 5 files changed, 27 insertions(+), 30 deletions(-) diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index bbca6729..b88d53de 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -342,7 +342,7 @@ size_t ZSTDMT_initializeCCtxParameters(ZSTD_CCtx_params* params, unsigned nbThre static size_t ZSTD_ldm_initializeParameters(ldmParams_t* params, U32 enableLdm) { - assert(LDM_BUCKET_SIZE_LOG <= ZSTD_LDM_BUCKETSIZELOG_MAX); + ZSTD_STATIC_ASSERT(LDM_BUCKET_SIZE_LOG <= ZSTD_LDM_BUCKETSIZELOG_MAX); params->enableLdm = enableLdm>0; params->hashLog = LDM_HASH_LOG; params->bucketSizeLog = LDM_BUCKET_SIZE_LOG; @@ -3568,8 +3568,6 @@ size_t ZSTD_compressBlock_ldm_generic(ZSTD_CCtx* cctx, } ip += rLength; anchor = ip; - - continue; /* faster when present ... (?) */ } } @@ -3836,12 +3834,11 @@ static size_t ZSTD_compressBlock_internal(ZSTD_CCtx* zc, void* dst, size_t dstCa const U32 current = (U32)(istart-base); size_t lastLLSize; const BYTE* anchor; + U32 const extDict = zc->lowLimit < zc->dictLimit; const ZSTD_blockCompressor blockCompressor = - zc->appliedParams.ldmParams.enableLdm ? - (zc->lowLimit < zc->dictLimit ? ZSTD_compressBlock_ldm_extDict : - ZSTD_compressBlock_ldm) : - ZSTD_selectBlockCompressor(zc->appliedParams.cParams.strategy, - zc->lowLimit < zc->dictLimit); + zc->appliedParams.ldmParams.enableLdm + ? (extDict ? ZSTD_compressBlock_ldm_extDict : ZSTD_compressBlock_ldm) + : ZSTD_selectBlockCompressor(zc->appliedParams.cParams.strategy, extDict); if (srcSize < MIN_CBLOCK_SIZE+ZSTD_blockHeaderSize+1) return 0; /* don't even attempt compression below a certain srcSize */ ZSTD_resetSeqStore(&(zc->seqStore)); diff --git a/lib/zstd.h b/lib/zstd.h index 4879c4a6..662657b2 100644 --- a/lib/zstd.h +++ b/lib/zstd.h @@ -979,7 +979,7 @@ typedef enum { /* advanced parameters - may not remain available after API update */ ZSTD_p_forceMaxWindow=1100, /* Force back-reference distances to remain < windowSize, * even when referencing into Dictionary content (default:0) */ - ZSTD_p_enableLongDistanceMatching, /* Enable long distance matching. + ZSTD_p_enableLongDistanceMatching=1200, /* Enable long distance matching. * This parameter is designed to improve the compression * ratio for large inputs with long distance matches. * This increases the memory usage as well as window size. diff --git a/programs/zstd.1 b/programs/zstd.1 index 13c804ae..0fad1d27 100644 --- a/programs/zstd.1 +++ b/programs/zstd.1 @@ -327,7 +327,7 @@ Determine \fBoverlapSize\fR, amount of data reloaded from previous job\. This pa The minimum \fIovlog\fR is 0, and the maximum is 9\. 0 means "no overlap", hence completely independent jobs\. 9 means "full overlap", meaning up to \fBwindowSize\fR is reloaded from previous job\. Reducing \fIovlog\fR by 1 reduces the amount of reload by a factor 2\. Default \fIovlog\fR is 6, which means "reload \fBwindowSize / 8\fR"\. Exception : the maximum compression level (22) has a default \fIovlog\fR of 9\. . .TP -\fBldmHashLog\fR=\fIldmHlog\fR, \fBldmHlog\fR=\fIldmHlog\fR +\fBldmHashLog\fR=\fIldmhlog\fR, \fBldmhlog\fR=\fIldmhlog\fR Specify the maximum size for a hash table used for long distance matching\. . .IP @@ -337,10 +337,10 @@ This option is ignored unless long distance matching is enabled\. Bigger hash tables usually improve compression ratio at the expense of more memory during compression and a decrease in compression speed\. . .IP -The minimum \fIldmHlog\fR is 6 and the maximum is 26 (default: 20)\. +The minimum \fIldmhlog\fR is 6 and the maximum is 26 (default: 20)\. . .TP -\fBldmSearchLength\fR=\fIldmSlen\fR, \fBldmSlen\fR=\fIldmSlen\fR +\fBldmSearchLength\fR=\fIldmslen\fR, \fBldmSlen\fR=\fIldmslen\fR Specify the minimum searched length of a match for long distance matching\. . .IP @@ -350,10 +350,10 @@ This option is ignored unless long distance matching is enabled\. Larger/very small values usually decrease compression ratio\. . .IP -The minumum \fIldmSlen\fR is 4 and the maximum is 4096 (default: 64)\. +The minumum \fIldmslen\fR is 4 and the maximum is 4096 (default: 64)\. . .TP -\fBldmBucketSizeLog\fR=\fIldmBucketSizeLog\fR +\fBldmBucketSizeLog\fR=\fIldmblog\fR, \fBldmblog\fR=\fIldmblog\fR Specify the size of each bucket for the hash table used for long distance matching\. . .IP @@ -363,10 +363,10 @@ This option is ignored unless long distance matching is enabled\. Larger bucket sizes improve collision resolution but decrease compression speed\. . .IP -The minimum \fIldmBucketSizeLog\fR is 0 and the maximum is 8 (default: 3)\. +The minimum \fIldmblog\fR is 0 and the maximum is 8 (default: 3)\. . .TP -\fBldmHashEveryLog\fR=\fIldmHashEveryLog\fR +\fBldmHashEveryLog\fR=\fIldmhevery\fR, \fBldmhevery\fR=\fIldmhevery\fR Specify the frequency of inserting entries into the long distance matching hash table\. . .IP @@ -376,7 +376,7 @@ This option is ignored unless long distance matching is enabled\. Larger values will improve compression speed\. Deviating far from the default value will likely result in a decrease in compression ratio\. . .IP -The default value is \fBwLog \- ldmHlog\fR\. +The default value is \fBwlog \- ldmhlog\fR\. . .SS "\-B#:" Select the size of each compression job\. This parameter is available only when multi\-threading is enabled\. Default value is \fB4 * windowSize\fR, which means it varies depending on compression level\. \fB\-B#\fR makes it possible to select a custom value\. Note that job size must respect a minimum value which is enforced transparently\. This minimum is either 1 MB, or \fBoverlapSize\fR, whichever is largest\. diff --git a/programs/zstd.1.md b/programs/zstd.1.md index 5f6aa450..2fcedde3 100644 --- a/programs/zstd.1.md +++ b/programs/zstd.1.md @@ -333,7 +333,7 @@ The list of available _options_: Default _ovlog_ is 6, which means "reload `windowSize / 8`". Exception : the maximum compression level (22) has a default _ovlog_ of 9. -- `ldmHashLog`=_ldmHlog_, `ldmHlog`=_ldmHlog_: +- `ldmHashLog`=_ldmhlog_, `ldmhlog`=_ldmhlog_: Specify the maximum size for a hash table used for long distance matching. This option is ignored unless long distance matching is enabled. @@ -341,18 +341,18 @@ The list of available _options_: Bigger hash tables usually improve compression ratio at the expense of more memory during compression and a decrease in compression speed. - The minimum _ldmHlog_ is 6 and the maximum is 26 (default: 20). + The minimum _ldmhlog_ is 6 and the maximum is 26 (default: 20). -- `ldmSearchLength`=_ldmSlen_, `ldmSlen`=_ldmSlen_: +- `ldmSearchLength`=_ldmslen_, `ldmslen`=_ldmslen_: Specify the minimum searched length of a match for long distance matching. This option is ignored unless long distance matching is enabled. Larger/very small values usually decrease compression ratio. - The minumum _ldmSlen_ is 4 and the maximum is 4096 (default: 64). + The minumum _ldmslen_ is 4 and the maximum is 4096 (default: 64). -- `ldmBucketSizeLog`=_ldmBucketSizeLog_: +- `ldmBucketSizeLog`=_ldmblog_, `ldmblog`=_ldmblog_: Specify the size of each bucket for the hash table used for long distance matching. @@ -361,9 +361,9 @@ The list of available _options_: Larger bucket sizes improve collision resolution but decrease compression speed. - The minimum _ldmBucketSizeLog_ is 0 and the maximum is 8 (default: 3). + The minimum _ldmblog_ is 0 and the maximum is 8 (default: 3). -- `ldmHashEveryLog`=_ldmHashEveryLog_: +- `ldmHashEveryLog`=_ldmhevery_, `ldmhevery`=_ldmhevery_: Specify the frequency of inserting entries into the long distance matching hash table. @@ -372,8 +372,8 @@ The list of available _options_: Larger values will improve compression speed. Deviating far from the default value will likely result in a decrease in compression ratio. - The default value is `wLog - ldmHlog`. - + The default value is `wlog - ldmhlog`. + ### -B#: Select the size of each compression job. This parameter is available only when multi-threading is enabled. diff --git a/programs/zstdcli.c b/programs/zstdcli.c index a60537a0..a3d50851 100644 --- a/programs/zstdcli.c +++ b/programs/zstdcli.c @@ -310,10 +310,10 @@ static unsigned parseCompressionParameters(const char* stringPtr, ZSTD_compressi if (longCommandWArg(&stringPtr, "targetLength=") || longCommandWArg(&stringPtr, "tlen=")) { params->targetLength = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; } if (longCommandWArg(&stringPtr, "strategy=") || longCommandWArg(&stringPtr, "strat=")) { params->strategy = (ZSTD_strategy)(readU32FromChar(&stringPtr)); if (stringPtr[0]==',') { stringPtr++; continue; } else break; } if (longCommandWArg(&stringPtr, "overlapLog=") || longCommandWArg(&stringPtr, "ovlog=")) { g_overlapLog = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; } - if (longCommandWArg(&stringPtr, "ldmHashLog=") || longCommandWArg(&stringPtr, "ldmHlog=")) { g_ldmHashLog = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; } - if (longCommandWArg(&stringPtr, "ldmSearchLength=") || longCommandWArg(&stringPtr, "ldmSlen=")) { g_ldmMinMatch = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; } - if (longCommandWArg(&stringPtr, "ldmBucketSizeLog=")) { g_ldmBucketSizeLog = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; } - if (longCommandWArg(&stringPtr, "ldmHashEveryLog=")) { g_ldmHashEveryLog = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; } + if (longCommandWArg(&stringPtr, "ldmHashLog=") || longCommandWArg(&stringPtr, "ldmhlog=")) { g_ldmHashLog = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; } + if (longCommandWArg(&stringPtr, "ldmSearchLength=") || longCommandWArg(&stringPtr, "ldmslen=")) { g_ldmMinMatch = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; } + if (longCommandWArg(&stringPtr, "ldmBucketSizeLog=") || longCommandWArg(&stringPtr, "ldmblog")) { g_ldmBucketSizeLog = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; } + if (longCommandWArg(&stringPtr, "ldmHashEveryLog=") || longCommandWArg(&stringPtr, "ldmhevery")) { g_ldmHashEveryLog = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; } return 0; } From 9e4060200b08623fe03daa6f95125d0e575eb0a2 Mon Sep 17 00:00:00 2001 From: Stella Lau Date: Wed, 6 Sep 2017 08:39:46 -0700 Subject: [PATCH 10/15] Add tests and fix pointer alignment --- lib/compress/zstd_compress.c | 43 ++++++++++++++++++++++-------------- lib/zstd.h | 8 +++---- tests/zstreamtest.c | 12 ++++++++++ 3 files changed, 42 insertions(+), 21 deletions(-) diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index b88d53de..99d5d796 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -532,7 +532,7 @@ size_t ZSTD_CCtxParam_setParameter( case ZSTD_p_ldmMinMatch : if (value == 0) return 0; - CLAMPCHECK(value, ZSTD_LDM_SEARCHLENGTH_MIN, ZSTD_LDM_SEARCHLENGTH_MAX); + CLAMPCHECK(value, ZSTD_LDM_MINMATCH_MIN, ZSTD_LDM_MINMATCH_MAX); params->ldmParams.minMatchLength = value; return 0; @@ -929,7 +929,7 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, 0 : params.cParams.windowLog - params.ldmParams.hashLog; } params.ldmParams.bucketSizeLog = - MIN(params.ldmParams.bucketSizeLog, params.ldmParams.hashLog); + MIN(params.ldmParams.bucketSizeLog, params.ldmParams.hashLog); zc->ldmState.hashPower = ZSTD_ldm_getHashPower(params.ldmParams.minMatchLength); } @@ -949,10 +949,6 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, size_t const buffInSize = (zbuff==ZSTDb_buffered) ? ((size_t)1 << params.cParams.windowLog) + blockSize : 0; void* ptr; - size_t const ldmSpace = params.ldmParams.enableLdm ? - ZSTD_ldm_getTableSize(params.ldmParams.hashLog, - params.ldmParams.bucketSizeLog) : 0; - /* Check if workSpace is large enough, alloc a new one if needed */ { size_t const entropySpace = sizeof(ZSTD_entropyCTables_t); size_t const optPotentialSpace = ((MaxML+1) + (MaxLL+1) + (MaxOff+1) + (1<optState.priceTable + ZSTD_OPT_NUM+1; } - /* ldm space */ - /* TODO */ + /* ldm hash table */ + /* initialize bucketOffsets table later for pointer alignment */ if (params.ldmParams.enableLdm) { size_t const ldmHSize = ((size_t)1) << params.ldmParams.hashLog; - size_t const ldmBucketSize = - ((size_t)1) << (params.ldmParams.hashLog - params.ldmParams.bucketSizeLog); - memset(ptr, 0, ldmSpace); + memset(ptr, 0, ldmHSize * sizeof(ldmEntry_t)); assert(((size_t)ptr & 3) == 0); /* ensure ptr is properly aligned */ zc->ldmState.hashTable = (ldmEntry_t*)ptr; ptr = zc->ldmState.hashTable + ldmHSize; - zc->ldmState.bucketOffsets = (BYTE*)ptr; - ptr = zc->ldmState.bucketOffsets + ldmBucketSize; } /* table Space */ @@ -1060,6 +1055,17 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, zc->seqStore.litStart = zc->seqStore.ofCode + maxNbSeq; ptr = zc->seqStore.litStart + blockSize; + /* ldm bucketOffsets table */ + if (params.ldmParams.enableLdm) { + size_t const ldmBucketSize = + ((size_t)1) << (params.ldmParams.hashLog - + params.ldmParams.bucketSizeLog); + assert(params.ldmParams.hashLog >= params.ldmParams.bucketSizeLog); + memset(ptr, 0, ldmBucketSize); + zc->ldmState.bucketOffsets = (BYTE*)ptr; + ptr = zc->ldmState.bucketOffsets + ldmBucketSize; + } + /* buffers */ zc->inBuffSize = buffInSize; zc->inBuff = (char*)ptr; @@ -3159,11 +3165,12 @@ static ZSTD_blockCompressor ZSTD_selectBlockCompressor(ZSTD_strategy strat, int /** ZSTD_ldm_getSmallHash() : * numBits should be <= 32 - * @return : the most significant numBits of value */ + * If numBits==0, returns 0. + * @return : the most significant numBits of value. */ static U32 ZSTD_ldm_getSmallHash(U64 value, U32 numBits) { assert(numBits <= 32); - return (U32)(value >> (64 - numBits)); + return numBits == 0 ? 0 : (U32)(value >> (64 - numBits)); } /** ZSTD_ldm_getChecksum() : @@ -3183,6 +3190,7 @@ static U32 ZSTD_ldm_getChecksum(U64 hash, U32 numBitsToDiscard) * numTagBits bits. */ static U32 ZSTD_ldm_getTag(U64 hash, U32 hbits, U32 numTagBits) { + assert(numTagBits <= 32 && hbits <= 32); if (32 - hbits < numTagBits) { return hash & ((1 << numTagBits) - 1); } else { @@ -3221,7 +3229,8 @@ static void ZSTD_ldm_insertEntry(ldmState_t* ldmState, * of rollingHash. The checksum is the next 32 most significant bits, followed * by ldmParams.hashEveryLog bits that make up the tag. */ static void ZSTD_ldm_makeEntryAndInsertByTag(ldmState_t* ldmState, - U64 rollingHash, U32 hBits, + U64 const rollingHash, + U32 const hBits, U32 const offset, ldmParams_t const ldmParams) { @@ -3272,7 +3281,7 @@ static U64 ZSTD_ldm_ipow(U64 base, U64 exp) } static U64 ZSTD_ldm_getHashPower(U32 minMatchLength) { - assert(minMatchLength >= ZSTD_LDM_SEARCHLENGTH_MIN); + assert(minMatchLength >= ZSTD_LDM_MINMATCH_MIN); return ZSTD_ldm_ipow(prime8bytes, minMatchLength - 1); } diff --git a/lib/zstd.h b/lib/zstd.h index 662657b2..794a855a 100644 --- a/lib/zstd.h +++ b/lib/zstd.h @@ -390,8 +390,8 @@ ZSTDLIB_API size_t ZSTD_DStreamOutSize(void); /*!< recommended size for output #define ZSTD_SEARCHLENGTH_MIN 3 /* only for ZSTD_btopt, other strategies are limited to 4 */ #define ZSTD_TARGETLENGTH_MIN 4 #define ZSTD_TARGETLENGTH_MAX 999 -#define ZSTD_LDM_SEARCHLENGTH_MIN 4 -#define ZSTD_LDM_SEARCHLENGTH_MAX 4096 +#define ZSTD_LDM_MINMATCH_MIN 4 +#define ZSTD_LDM_MINMATCH_MAX 4096 #define ZSTD_LDM_BUCKETSIZELOG_MAX 8 #define ZSTD_FRAMEHEADERSIZE_MAX 18 /* for static allocation */ @@ -996,8 +996,8 @@ typedef enum { * (default: 20). */ ZSTD_p_ldmMinMatch, /* Minimum size of searched matches for long distance matcher. * Larger/too small values usually decrease compression ratio. - * Must be clamped between ZSTD_LDM_SEARCHLENGTH_MIN - * and ZSTD_LDM_SEARCHLENGTH_MAX (default: 64). */ + * Must be clamped between ZSTD_LDM_MINMATCH_MIN + * and ZSTD_LDM_MINMATCH_MAX (default: 64). */ ZSTD_p_ldmBucketSizeLog, /* Log size of each bucket in the LDM hash table for collision resolution. * Larger values usually improve collision resolution but may decrease * compression speed. diff --git a/tests/zstreamtest.c b/tests/zstreamtest.c index 76495d45..1a9aa506 100644 --- a/tests/zstreamtest.c +++ b/tests/zstreamtest.c @@ -709,6 +709,13 @@ static size_t FUZ_randomLength(U32* seed, U32 maxLog) #define MIN(a,b) ( (a) < (b) ? (a) : (b) ) +/* Return value in range minVal <= v <= maxVal */ +static U32 FUZ_randomClampedLength(U32* seed, U32 minVal, U32 maxVal) +{ + U32 const mod = maxVal < minVal ? 1 : (maxVal + 1) - minVal; + return (U32)((FUZ_rand(seed) % mod) + minVal); +} + #define CHECK(cond, ...) { \ if (cond) { \ DISPLAY("Error => "); \ @@ -1380,7 +1387,12 @@ static int fuzzerTests_newAPI(U32 seed, U32 nbTests, unsigned startTest, double if (FUZ_rand(&lseed) & 1) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_minMatch, cParams.searchLength, useOpaqueAPI) ); if (FUZ_rand(&lseed) & 1) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_targetLength, cParams.targetLength, useOpaqueAPI) ); + /* mess with long distance matching parameters */ if (FUZ_rand(&lseed) & 1) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_enableLongDistanceMatching, FUZ_rand(&lseed) & 63, useOpaqueAPI) ); + if (FUZ_rand(&lseed) & 3) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_ldmHashLog, FUZ_randomClampedLength(&lseed, ZSTD_HASHLOG_MIN, ZSTD_HASHLOG_MAX), useOpaqueAPI) ); + if (FUZ_rand(&lseed) & 3) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_ldmMinMatch, FUZ_randomClampedLength(&lseed, ZSTD_LDM_MINMATCH_MIN, ZSTD_LDM_MINMATCH_MAX), useOpaqueAPI) ); + if (FUZ_rand(&lseed) & 3) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_ldmBucketSizeLog, FUZ_randomClampedLength(&lseed, 0, ZSTD_LDM_BUCKETSIZELOG_MAX), useOpaqueAPI) ); + if (FUZ_rand(&lseed) & 3) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_ldmHashEveryLog, FUZ_randomClampedLength(&lseed, 0, ZSTD_WINDOWLOG_MAX - ZSTD_HASHLOG_MIN), useOpaqueAPI) ); /* unconditionally set, to be sync with decoder */ /* mess with frame parameters */ From 8c33cfe0bc167274415f711643ce39cc02c0848f Mon Sep 17 00:00:00 2001 From: Stella Lau Date: Wed, 6 Sep 2017 11:03:35 -0700 Subject: [PATCH 11/15] Add ldm documentation in README --- doc/images/ldmCspeed.png | Bin 0 -> 72251 bytes doc/images/ldmDspeed.png | Bin 0 -> 27594 bytes programs/README.md | 58 +++++++++++++++++++++++++++++++++++++++ programs/zstdcli.c | 2 +- 4 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 doc/images/ldmCspeed.png create mode 100644 doc/images/ldmDspeed.png diff --git a/doc/images/ldmCspeed.png b/doc/images/ldmCspeed.png new file mode 100644 index 0000000000000000000000000000000000000000..d3bfce4c80123bc9bd6ef8f46c76fe79065defa8 GIT binary patch literal 72251 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 literal 0 HcmV?d00001 diff --git a/doc/images/ldmDspeed.png b/doc/images/ldmDspeed.png new file mode 100644 index 0000000000000000000000000000000000000000..d5445f018adedc0f0c0ea7880782bc0bafb35c5f GIT binary patch literal 27594 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 v?Vg=!Nz;yEY59Ek*LRtj$#X)Hlr4D(ah#jg6X Date: Wed, 6 Sep 2017 15:57:26 -0700 Subject: [PATCH 12/15] Remove debug code --- lib/compress/zstd_compress.c | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 99d5d796..2364eee2 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -201,33 +201,6 @@ size_t ZSTD_sizeof_CCtx(const ZSTD_CCtx* cctx) + cctx->outBuffSize + cctx->inBuffSize + ZSTDMT_sizeof_CCtx(cctx->mtctx); } -#if 0 -static void ZSTD_debugPrintCCtxParams(ZSTD_CCtx_params* params) -{ - DEBUGLOG(2, "======CCtxParams======"); - DEBUGLOG(2, "cParams: %u %u %u %u %u %u %u", - params->cParams.windowLog, - params->cParams.chainLog, - params->cParams.hashLog, - params->cParams.searchLog, - params->cParams.searchLength, - params->cParams.targetLength, - params->cParams.strategy); - DEBUGLOG(2, "fParams: %u %u %u", - params->fParams.contentSizeFlag, - params->fParams.checksumFlag, - params->fParams.noDictIDFlag); - DEBUGLOG(2, "cLevel, forceWindow: %u %u", - params->compressionLevel, - params->forceWindow); - DEBUGLOG(2, "ldm: %u %u %u %u %u", - params->ldmParams.enableLdm, - params->ldmParams.hashLog, - params->ldmParams.bucketSizeLog, - params->ldmParams.minMatchLength, - params->ldmParams.hashEveryLog); -} -#endif size_t ZSTD_sizeof_CStream(const ZSTD_CStream* zcs) { From 360428c5d94d97bf481227c70eedcf7ddd9a0576 Mon Sep 17 00:00:00 2001 From: Stella Lau Date: Wed, 6 Sep 2017 17:56:01 -0700 Subject: [PATCH 13/15] Move ldm functions to their own file --- build/VS2008/fullbench/fullbench.vcproj | 8 + build/VS2008/fuzzer/fuzzer.vcproj | 8 + build/VS2008/zstd/zstd.vcproj | 8 + build/VS2008/zstdlib/zstdlib.vcproj | 8 + build/VS2010/fullbench/fullbench.vcxproj | 2 + build/VS2010/fuzzer/fuzzer.vcxproj | 2 + build/VS2010/libzstd-dll/libzstd-dll.vcxproj | 2 + build/VS2010/libzstd/libzstd.vcxproj | 2 + build/VS2010/zstd/zstd.vcxproj | 2 + build/cmake/lib/CMakeLists.txt | 2 + lib/compress/zstd_compress.c | 745 +------------------ lib/compress/zstd_ldm.c | 702 +++++++++++++++++ lib/compress/zstd_ldm.h | 67 ++ 13 files changed, 820 insertions(+), 738 deletions(-) create mode 100644 lib/compress/zstd_ldm.c create mode 100644 lib/compress/zstd_ldm.h diff --git a/build/VS2008/fullbench/fullbench.vcproj b/build/VS2008/fullbench/fullbench.vcproj index 05ec5ca0..715ea257 100644 --- a/build/VS2008/fullbench/fullbench.vcproj +++ b/build/VS2008/fullbench/fullbench.vcproj @@ -403,6 +403,10 @@ + + + + diff --git a/build/VS2008/fuzzer/fuzzer.vcproj b/build/VS2008/fuzzer/fuzzer.vcproj index 700dd7eb..1421619a 100644 --- a/build/VS2008/fuzzer/fuzzer.vcproj +++ b/build/VS2008/fuzzer/fuzzer.vcproj @@ -415,6 +415,10 @@ + + + + diff --git a/build/VS2008/zstd/zstd.vcproj b/build/VS2008/zstd/zstd.vcproj index 86dd3a25..dbd211c0 100644 --- a/build/VS2008/zstd/zstd.vcproj +++ b/build/VS2008/zstd/zstd.vcproj @@ -459,6 +459,10 @@ + + + + diff --git a/build/VS2008/zstdlib/zstdlib.vcproj b/build/VS2008/zstdlib/zstdlib.vcproj index ac8f896c..340a4cd8 100644 --- a/build/VS2008/zstdlib/zstdlib.vcproj +++ b/build/VS2008/zstdlib/zstdlib.vcproj @@ -403,6 +403,10 @@ + + + + + @@ -189,6 +190,7 @@ + diff --git a/build/VS2010/fuzzer/fuzzer.vcxproj b/build/VS2010/fuzzer/fuzzer.vcxproj index 9f00899d..6fe32720 100644 --- a/build/VS2010/fuzzer/fuzzer.vcxproj +++ b/build/VS2010/fuzzer/fuzzer.vcxproj @@ -169,6 +169,7 @@ + @@ -192,6 +193,7 @@ + diff --git a/build/VS2010/libzstd-dll/libzstd-dll.vcxproj b/build/VS2010/libzstd-dll/libzstd-dll.vcxproj index 0a4be69d..2d04c693 100644 --- a/build/VS2010/libzstd-dll/libzstd-dll.vcxproj +++ b/build/VS2010/libzstd-dll/libzstd-dll.vcxproj @@ -33,6 +33,7 @@ + @@ -76,6 +77,7 @@ + diff --git a/build/VS2010/libzstd/libzstd.vcxproj b/build/VS2010/libzstd/libzstd.vcxproj index 51b84067..c01a5d17 100644 --- a/build/VS2010/libzstd/libzstd.vcxproj +++ b/build/VS2010/libzstd/libzstd.vcxproj @@ -33,6 +33,7 @@ + @@ -76,6 +77,7 @@ + diff --git a/build/VS2010/zstd/zstd.vcxproj b/build/VS2010/zstd/zstd.vcxproj index 90470180..ace34346 100644 --- a/build/VS2010/zstd/zstd.vcxproj +++ b/build/VS2010/zstd/zstd.vcxproj @@ -34,6 +34,7 @@ + @@ -69,6 +70,7 @@ + diff --git a/build/cmake/lib/CMakeLists.txt b/build/cmake/lib/CMakeLists.txt index f4b7e375..f5d2eff9 100644 --- a/build/cmake/lib/CMakeLists.txt +++ b/build/cmake/lib/CMakeLists.txt @@ -42,6 +42,7 @@ SET(Sources ${LIBRARY_DIR}/compress/zstd_double_fast.c ${LIBRARY_DIR}/compress/zstd_lazy.c ${LIBRARY_DIR}/compress/zstd_opt.c + ${LIBRARY_DIR}/compress/zstd_ldm.c ${LIBRARY_DIR}/decompress/huf_decompress.c ${LIBRARY_DIR}/decompress/zstd_decompress.c ${LIBRARY_DIR}/dictBuilder/cover.c @@ -67,6 +68,7 @@ SET(Headers ${LIBRARY_DIR}/compress/zstd_double_fast.h ${LIBRARY_DIR}/compress/zstd_lazy.h ${LIBRARY_DIR}/compress/zstd_opt.h + ${LIBRARY_DIR}/compress/zstd_ldm.h ${LIBRARY_DIR}/compress/zstdmt_compress.h ${LIBRARY_DIR}/dictBuilder/zdict.h ${LIBRARY_DIR}/deprecated/zbuff.h) diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 3abc1e91..5ea00b8e 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -30,14 +30,7 @@ #include "zstd_double_fast.h" #include "zstd_lazy.h" #include "zstd_opt.h" - - -#define LDM_BUCKET_SIZE_LOG 3 -#define LDM_MIN_MATCH_LENGTH 64 -#define LDM_WINDOW_LOG 27 -#define LDM_HASH_LOG 20 -#define LDM_HASH_CHAR_OFFSET 10 -#define LDM_HASHEVERYLOG_NOTSET 9999 +#include "zstd_ldm.h" /*-************************************* @@ -135,33 +128,6 @@ size_t ZSTD_sizeof_CCtx(const ZSTD_CCtx* cctx) + cctx->outBuffSize + cctx->inBuffSize + ZSTDMT_sizeof_CCtx(cctx->mtctx); } -#if 0 -static void ZSTD_debugPrintCCtxParams(ZSTD_CCtx_params* params) -{ - DEBUGLOG(2, "======CCtxParams======"); - DEBUGLOG(2, "cParams: %u %u %u %u %u %u %u", - params->cParams.windowLog, - params->cParams.chainLog, - params->cParams.hashLog, - params->cParams.searchLog, - params->cParams.searchLength, - params->cParams.targetLength, - params->cParams.strategy); - DEBUGLOG(2, "fParams: %u %u %u", - params->fParams.contentSizeFlag, - params->fParams.checksumFlag, - params->fParams.noDictIDFlag); - DEBUGLOG(2, "cLevel, forceWindow: %u %u", - params->compressionLevel, - params->forceWindow); - DEBUGLOG(2, "ldm: %u %u %u %u %u", - params->ldmParams.enableLdm, - params->ldmParams.hashLog, - params->ldmParams.bucketSizeLog, - params->ldmParams.minMatchLength, - params->ldmParams.hashEveryLog); -} -#endif size_t ZSTD_sizeof_CStream(const ZSTD_CStream* zcs) { @@ -274,17 +240,6 @@ size_t ZSTDMT_CCtxParam_setMTCtxParameter( ZSTD_CCtx_params* params, ZSTDMT_parameter parameter, unsigned value); size_t ZSTDMT_initializeCCtxParameters(ZSTD_CCtx_params* params, unsigned nbThreads); -static size_t ZSTD_ldm_initializeParameters(ldmParams_t* params, U32 enableLdm) -{ - ZSTD_STATIC_ASSERT(LDM_BUCKET_SIZE_LOG <= ZSTD_LDM_BUCKETSIZELOG_MAX); - params->enableLdm = enableLdm>0; - params->hashLog = LDM_HASH_LOG; - params->bucketSizeLog = LDM_BUCKET_SIZE_LOG; - params->minMatchLength = LDM_MIN_MATCH_LENGTH; - params->hashEveryLog = LDM_HASHEVERYLOG_NOTSET; - return 0; -} - size_t ZSTD_CCtx_setParameter(ZSTD_CCtx* cctx, ZSTD_cParameter param, unsigned value) { if (cctx->streamStage != zcss_init) return ERROR(stage_wrong); @@ -454,7 +409,7 @@ size_t ZSTD_CCtxParam_setParameter( case ZSTD_p_enableLongDistanceMatching : if (value != 0) { ZSTD_cLevelToCCtxParams(params); - params->cParams.windowLog = LDM_WINDOW_LOG; + params->cParams.windowLog = ZSTD_LDM_WINDOW_LOG; } return ZSTD_ldm_initializeParameters(¶ms->ldmParams, value); @@ -689,15 +644,6 @@ ZSTD_compressionParameters ZSTD_adjustCParams(ZSTD_compressionParameters cPar, u return ZSTD_adjustCParams_internal(cPar, srcSize, dictSize); } -/* Estimate the space needed for long distance matching tables. */ -static size_t ZSTD_ldm_getTableSize(U32 hashLog, U32 bucketSizeLog) { - size_t const ldmHSize = ((size_t)1) << hashLog; - size_t const ldmBucketSizeLog = MIN(bucketSizeLog, hashLog); - size_t const ldmBucketSize = - ((size_t)1) << (hashLog - ldmBucketSizeLog); - return ldmBucketSize + (ldmHSize * (sizeof(ldmEntry_t))); -} - size_t ZSTD_estimateCCtxSize_advanced_usingCCtxParams(const ZSTD_CCtx_params* params) { /* Estimate CCtx size is supported for single-threaded compression only. */ @@ -832,8 +778,6 @@ static size_t ZSTD_continueCCtx(ZSTD_CCtx* cctx, ZSTD_CCtx_params params, U64 pl typedef enum { ZSTDcrp_continue, ZSTDcrp_noMemset } ZSTD_compResetPolicy_e; typedef enum { ZSTDb_not_buffered, ZSTDb_buffered } ZSTD_buffered_policy_e; -static U64 ZSTD_ldm_getHashPower(U32 minMatchLength); - /*! ZSTD_resetCCtx_internal() : note : `params` are assumed fully validated at this stage */ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, @@ -847,7 +791,7 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, if (ZSTD_equivalentParams(params, zc->appliedParams)) { DEBUGLOG(5, "ZSTD_equivalentParams()==1"); assert(!(params.ldmParams.enableLdm && - params.ldmParams.hashEveryLog == LDM_HASHEVERYLOG_NOTSET)); + params.ldmParams.hashEveryLog == ZSTD_LDM_HASHEVERYLOG_NOTSET)); zc->entropy->hufCTable_repeatMode = HUF_repeat_none; zc->entropy->offcode_repeatMode = FSE_repeat_none; zc->entropy->matchlength_repeatMode = FSE_repeat_none; @@ -857,13 +801,8 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, if (params.ldmParams.enableLdm) { /* Adjust long distance matching parameters */ - if (params.ldmParams.hashEveryLog == LDM_HASHEVERYLOG_NOTSET) { - params.ldmParams.hashEveryLog = - params.cParams.windowLog < params.ldmParams.hashLog ? - 0 : params.cParams.windowLog - params.ldmParams.hashLog; - } - params.ldmParams.bucketSizeLog = - MIN(params.ldmParams.bucketSizeLog, params.ldmParams.hashLog); + ZSTD_ldm_adjustParameters(¶ms.ldmParams, params.cParams.windowLog); + assert(params.ldmParams.hashLog >= params.ldmParams.bucketSizeLog); zc->ldmState.hashPower = ZSTD_ldm_getHashPower(params.ldmParams.minMatchLength); } @@ -994,7 +933,6 @@ static size_t ZSTD_resetCCtx_internal(ZSTD_CCtx* zc, size_t const ldmBucketSize = ((size_t)1) << (params.ldmParams.hashLog - params.ldmParams.bucketSizeLog); - assert(params.ldmParams.hashLog >= params.ldmParams.bucketSizeLog); memset(ptr, 0, ldmBucketSize); zc->ldmState.bucketOffsets = (BYTE*)ptr; ptr = zc->ldmState.bucketOffsets + ldmBucketSize; @@ -1553,9 +1491,10 @@ MEM_STATIC size_t ZSTD_compressSequences(seqStore_t* seqStorePtr, } /* ZSTD_selectBlockCompressor() : + * Not static, but internal use only (used by long distance matcher) * assumption : strat is a valid strategy */ typedef size_t (*ZSTD_blockCompressor) (ZSTD_CCtx* ctx, const void* src, size_t srcSize); -static ZSTD_blockCompressor ZSTD_selectBlockCompressor(ZSTD_strategy strat, int extDict) +ZSTD_blockCompressor ZSTD_selectBlockCompressor(ZSTD_strategy strat, int extDict) { static const ZSTD_blockCompressor blockCompressor[2][(unsigned)ZSTD_btultra+1] = { { ZSTD_compressBlock_fast /* default for 0 */, @@ -1574,676 +1513,6 @@ static ZSTD_blockCompressor ZSTD_selectBlockCompressor(ZSTD_strategy strat, int return blockCompressor[extDict!=0][(U32)strat]; } -/*-************************************* -* Long distance matching -***************************************/ - -/** ZSTD_ldm_getSmallHash() : - * numBits should be <= 32 - * If numBits==0, returns 0. - * @return : the most significant numBits of value. */ -static U32 ZSTD_ldm_getSmallHash(U64 value, U32 numBits) -{ - assert(numBits <= 32); - return numBits == 0 ? 0 : (U32)(value >> (64 - numBits)); -} - -/** ZSTD_ldm_getChecksum() : - * numBitsToDiscard should be <= 32 - * @return : the next most significant 32 bits after numBitsToDiscard */ -static U32 ZSTD_ldm_getChecksum(U64 hash, U32 numBitsToDiscard) -{ - assert(numBitsToDiscard <= 32); - return (hash >> (64 - 32 - numBitsToDiscard)) & 0xFFFFFFFF; -} - -/** ZSTD_ldm_getTag() ; - * Given the hash, returns the most significant numTagBits bits - * after (32 + hbits) bits. - * - * If there are not enough bits remaining, return the last - * numTagBits bits. */ -static U32 ZSTD_ldm_getTag(U64 hash, U32 hbits, U32 numTagBits) -{ - assert(numTagBits <= 32 && hbits <= 32); - if (32 - hbits < numTagBits) { - return hash & ((1 << numTagBits) - 1); - } else { - return (hash >> (32 - hbits - numTagBits)) & ((1 << numTagBits) - 1); - } -} - -/** ZSTD_ldm_getBucket() : - * Returns a pointer to the start of the bucket associated with hash. */ -static ldmEntry_t* ZSTD_ldm_getBucket( - ldmState_t* ldmState, size_t hash, ldmParams_t const ldmParams) -{ - return ldmState->hashTable + (hash << ldmParams.bucketSizeLog); -} - -/** ZSTD_ldm_insertEntry() : - * Insert the entry with corresponding hash into the hash table */ -static void ZSTD_ldm_insertEntry(ldmState_t* ldmState, - size_t const hash, const ldmEntry_t entry, - ldmParams_t const ldmParams) -{ - BYTE* const bucketOffsets = ldmState->bucketOffsets; - *(ZSTD_ldm_getBucket(ldmState, hash, ldmParams) + bucketOffsets[hash]) = entry; - bucketOffsets[hash]++; - bucketOffsets[hash] &= (1 << ldmParams.bucketSizeLog) - 1; -} - -/** ZSTD_ldm_makeEntryAndInsertByTag() : - * - * Gets the small hash, checksum, and tag from the rollingHash. - * - * If the tag matches (1 << ldmParams.hashEveryLog)-1, then - * creates an ldmEntry from the offset, and inserts it into the hash table. - * - * hBits is the length of the small hash, which is the most significant hBits - * of rollingHash. The checksum is the next 32 most significant bits, followed - * by ldmParams.hashEveryLog bits that make up the tag. */ -static void ZSTD_ldm_makeEntryAndInsertByTag(ldmState_t* ldmState, - U64 const rollingHash, - U32 const hBits, - U32 const offset, - ldmParams_t const ldmParams) -{ - U32 const tag = ZSTD_ldm_getTag(rollingHash, hBits, ldmParams.hashEveryLog); - U32 const tagMask = (1 << ldmParams.hashEveryLog) - 1; - if (tag == tagMask) { - U32 const hash = ZSTD_ldm_getSmallHash(rollingHash, hBits); - U32 const checksum = ZSTD_ldm_getChecksum(rollingHash, hBits); - ldmEntry_t entry; - entry.offset = offset; - entry.checksum = checksum; - ZSTD_ldm_insertEntry(ldmState, hash, entry, ldmParams); - } -} - -/** ZSTD_ldm_getRollingHash() : - * Get a 64-bit hash using the first len bytes from buf. - * - * Giving bytes s = s_1, s_2, ... s_k, the hash is defined to be - * H(s) = s_1*(a^(k-1)) + s_2*(a^(k-2)) + ... + s_k*(a^0) - * - * where the constant a is defined to be prime8bytes. - * - * The implementation adds an offset to each byte, so - * H(s) = (s_1 + HASH_CHAR_OFFSET)*(a^(k-1)) + ... */ -static U64 ZSTD_ldm_getRollingHash(const BYTE* buf, U32 len) -{ - U64 ret = 0; - U32 i; - for (i = 0; i < len; i++) { - ret *= prime8bytes; - ret += buf[i] + LDM_HASH_CHAR_OFFSET; - } - return ret; -} - -/** ZSTD_ldm_ipow() : - * Return base^exp. */ -static U64 ZSTD_ldm_ipow(U64 base, U64 exp) -{ - U64 ret = 1; - while (exp) { - if (exp & 1) { ret *= base; } - exp >>= 1; - base *= base; - } - return ret; -} - -static U64 ZSTD_ldm_getHashPower(U32 minMatchLength) { - assert(minMatchLength >= ZSTD_LDM_MINMATCH_MIN); - return ZSTD_ldm_ipow(prime8bytes, minMatchLength - 1); -} - -/** ZSTD_ldm_updateHash() : - * Updates hash by removing toRemove and adding toAdd. */ -static U64 ZSTD_ldm_updateHash(U64 hash, BYTE toRemove, BYTE toAdd, U64 hashPower) -{ - hash -= ((toRemove + LDM_HASH_CHAR_OFFSET) * hashPower); - hash *= prime8bytes; - hash += toAdd + LDM_HASH_CHAR_OFFSET; - return hash; -} - -/** ZSTD_ldm_countBackwardsMatch() : - * Returns the number of bytes that match backwards before pIn and pMatch. - * - * We count only bytes where pMatch >= pBase and pIn >= pAnchor. */ -static size_t ZSTD_ldm_countBackwardsMatch( - const BYTE* pIn, const BYTE* pAnchor, - const BYTE* pMatch, const BYTE* pBase) -{ - size_t matchLength = 0; - while (pIn > pAnchor && pMatch > pBase && pIn[-1] == pMatch[-1]) { - pIn--; - pMatch--; - matchLength++; - } - return matchLength; -} - -/** ZSTD_ldm_fillFastTables() : - * - * Fills the relevant tables for the ZSTD_fast and ZSTD_dfast strategies. - * This is similar to ZSTD_loadDictionaryContent. - * - * The tables for the other strategies are filled within their - * block compressors. */ -static size_t ZSTD_ldm_fillFastTables(ZSTD_CCtx* zc, const void* end) -{ - const BYTE* const iend = (const BYTE*)end; - const U32 mls = zc->appliedParams.cParams.searchLength; - - switch(zc->appliedParams.cParams.strategy) - { - case ZSTD_fast: - ZSTD_fillHashTable(zc, iend, mls); - zc->nextToUpdate = (U32)(iend - zc->base); - break; - - case ZSTD_dfast: - ZSTD_fillDoubleHashTable(zc, iend, mls); - zc->nextToUpdate = (U32)(iend - zc->base); - break; - - case ZSTD_greedy: - case ZSTD_lazy: - case ZSTD_lazy2: - case ZSTD_btlazy2: - case ZSTD_btopt: - case ZSTD_btultra: - break; - default: - assert(0); /* not possible : not a valid strategy id */ - } - - return 0; -} - -/** ZSTD_ldm_fillLdmHashTable() : - * - * Fills hashTable from (lastHashed + 1) to iend (non-inclusive). - * lastHash is the rolling hash that corresponds to lastHashed. - * - * Returns the rolling hash corresponding to position iend-1. */ -static U64 ZSTD_ldm_fillLdmHashTable(ldmState_t* state, - U64 lastHash, const BYTE* lastHashed, - const BYTE* iend, const BYTE* base, - U32 hBits, ldmParams_t const ldmParams) -{ - U64 rollingHash = lastHash; - const BYTE* cur = lastHashed + 1; - - while (cur < iend) { - rollingHash = ZSTD_ldm_updateHash(rollingHash, cur[-1], - cur[ldmParams.minMatchLength-1], - state->hashPower); - ZSTD_ldm_makeEntryAndInsertByTag(state, - rollingHash, hBits, - (U32)(cur - base), ldmParams); - ++cur; - } - return rollingHash; -} - - -/** ZSTD_ldm_limitTableUpdate() : - * - * Sets cctx->nextToUpdate to a position corresponding closer to anchor - * if it is far way - * (after a long match, only update tables a limited amount). */ -static void ZSTD_ldm_limitTableUpdate(ZSTD_CCtx* cctx, const BYTE* anchor) -{ - U32 const current = (U32)(anchor - cctx->base); - if (current > cctx->nextToUpdate + 1024) { - cctx->nextToUpdate = - current - MIN(512, current - cctx->nextToUpdate - 1024); - } -} - -/** ZSTD_compressBlock_ldm_generic() : - * - * This is a block compressor intended for long distance matching. - * - * The function searches for matches of length at least - * ldmParams.minMatchLength using a hash table in cctx->ldmState. - * Matches can be at a distance of up to cParams.windowLog. - * - * Upon finding a match, the unmatched literals are compressed using a - * ZSTD_blockCompressor (depending on the strategy in the compression - * parameters), which stores the matched sequences. The "long distance" - * match is then stored with the remaining literals from the - * ZSTD_blockCompressor. */ -FORCE_INLINE_TEMPLATE -size_t ZSTD_compressBlock_ldm_generic(ZSTD_CCtx* cctx, - const void* src, size_t srcSize) -{ - ldmState_t* const ldmState = &(cctx->ldmState); - const ldmParams_t ldmParams = cctx->appliedParams.ldmParams; - const U64 hashPower = ldmState->hashPower; - const U32 hBits = ldmParams.hashLog - ldmParams.bucketSizeLog; - const U32 ldmBucketSize = (1 << ldmParams.bucketSizeLog); - const U32 ldmTagMask = (1 << ldmParams.hashEveryLog) - 1; - seqStore_t* const seqStorePtr = &(cctx->seqStore); - const BYTE* const base = cctx->base; - const BYTE* const istart = (const BYTE*)src; - const BYTE* ip = istart; - const BYTE* anchor = istart; - const U32 lowestIndex = cctx->dictLimit; - const BYTE* const lowest = base + lowestIndex; - const BYTE* const iend = istart + srcSize; - const BYTE* const ilimit = iend - ldmParams.minMatchLength; - - const ZSTD_blockCompressor blockCompressor = - ZSTD_selectBlockCompressor(cctx->appliedParams.cParams.strategy, 0); - U32* const repToConfirm = seqStorePtr->repToConfirm; - U32 savedRep[ZSTD_REP_NUM]; - U64 rollingHash = 0; - const BYTE* lastHashed = NULL; - size_t i, lastLiterals; - - /* Save seqStorePtr->rep and copy repToConfirm */ - for (i = 0; i < ZSTD_REP_NUM; i++) - savedRep[i] = repToConfirm[i] = seqStorePtr->rep[i]; - - /* Main Search Loop */ - while (ip < ilimit) { /* < instead of <=, because repcode check at (ip+1) */ - size_t mLength; - U32 const current = (U32)(ip - base); - size_t forwardMatchLength = 0, backwardMatchLength = 0; - ldmEntry_t* bestEntry = NULL; - if (ip != istart) { - rollingHash = ZSTD_ldm_updateHash(rollingHash, lastHashed[0], - lastHashed[ldmParams.minMatchLength], - hashPower); - } else { - rollingHash = ZSTD_ldm_getRollingHash(ip, ldmParams.minMatchLength); - } - lastHashed = ip; - - /* Do not insert and do not look for a match */ - if (ZSTD_ldm_getTag(rollingHash, hBits, ldmParams.hashEveryLog) != - ldmTagMask) { - ip++; - continue; - } - - /* Get the best entry and compute the match lengths */ - { - ldmEntry_t* const bucket = - ZSTD_ldm_getBucket(ldmState, - ZSTD_ldm_getSmallHash(rollingHash, hBits), - ldmParams); - ldmEntry_t* cur; - size_t bestMatchLength = 0; - U32 const checksum = ZSTD_ldm_getChecksum(rollingHash, hBits); - - for (cur = bucket; cur < bucket + ldmBucketSize; ++cur) { - const BYTE* const pMatch = cur->offset + base; - size_t curForwardMatchLength, curBackwardMatchLength, - curTotalMatchLength; - if (cur->checksum != checksum || cur->offset <= lowestIndex) { - continue; - } - - curForwardMatchLength = ZSTD_count(ip, pMatch, iend); - if (curForwardMatchLength < ldmParams.minMatchLength) { - continue; - } - curBackwardMatchLength = ZSTD_ldm_countBackwardsMatch( - ip, anchor, pMatch, lowest); - curTotalMatchLength = curForwardMatchLength + - curBackwardMatchLength; - - if (curTotalMatchLength > bestMatchLength) { - bestMatchLength = curTotalMatchLength; - forwardMatchLength = curForwardMatchLength; - backwardMatchLength = curBackwardMatchLength; - bestEntry = cur; - } - } - } - - /* No match found -- continue searching */ - if (bestEntry == NULL) { - ZSTD_ldm_makeEntryAndInsertByTag(ldmState, rollingHash, - hBits, current, - ldmParams); - ip++; - continue; - } - - /* Match found */ - mLength = forwardMatchLength + backwardMatchLength; - ip -= backwardMatchLength; - - /* Call the block compressor on the remaining literals */ - { - U32 const matchIndex = bestEntry->offset; - const BYTE* const match = base + matchIndex - backwardMatchLength; - U32 const offset = (U32)(ip - match); - - /* Overwrite rep codes */ - for (i = 0; i < ZSTD_REP_NUM; i++) - seqStorePtr->rep[i] = repToConfirm[i]; - - /* Fill tables for block compressor */ - ZSTD_ldm_limitTableUpdate(cctx, anchor); - ZSTD_ldm_fillFastTables(cctx, anchor); - - /* Call block compressor and get remaining literals */ - lastLiterals = blockCompressor(cctx, anchor, ip - anchor); - cctx->nextToUpdate = (U32)(ip - base); - - /* Update repToConfirm with the new offset */ - for (i = ZSTD_REP_NUM - 1; i > 0; i--) - repToConfirm[i] = repToConfirm[i-1]; - repToConfirm[0] = offset; - - /* Store the sequence with the leftover literals */ - ZSTD_storeSeq(seqStorePtr, lastLiterals, ip - lastLiterals, - offset + ZSTD_REP_MOVE, mLength - MINMATCH); - } - - /* Insert the current entry into the hash table */ - ZSTD_ldm_makeEntryAndInsertByTag(ldmState, rollingHash, hBits, - (U32)(lastHashed - base), - ldmParams); - - assert(ip + backwardMatchLength == lastHashed); - - /* Fill the hash table from lastHashed+1 to ip+mLength*/ - /* Heuristic: don't need to fill the entire table at end of block */ - if (ip + mLength < ilimit) { - rollingHash = ZSTD_ldm_fillLdmHashTable( - ldmState, rollingHash, lastHashed, - ip + mLength, base, hBits, ldmParams); - lastHashed = ip + mLength - 1; - } - ip += mLength; - anchor = ip; - /* Check immediate repcode */ - while ( (ip < ilimit) - && ( (repToConfirm[1] > 0) && (repToConfirm[1] <= (U32)(ip-lowest)) - && (MEM_read32(ip) == MEM_read32(ip - repToConfirm[1])) )) { - - size_t const rLength = ZSTD_count(ip+4, ip+4-repToConfirm[1], - iend) + 4; - /* Swap repToConfirm[1] <=> repToConfirm[0] */ - { - U32 const tmpOff = repToConfirm[1]; - repToConfirm[1] = repToConfirm[0]; - repToConfirm[0] = tmpOff; - } - - ZSTD_storeSeq(seqStorePtr, 0, anchor, 0, rLength-MINMATCH); - - /* Fill the hash table from lastHashed+1 to ip+rLength*/ - if (ip + rLength < ilimit) { - rollingHash = ZSTD_ldm_fillLdmHashTable( - ldmState, rollingHash, lastHashed, - ip + rLength, base, hBits, ldmParams); - lastHashed = ip + rLength - 1; - } - ip += rLength; - anchor = ip; - } - } - - /* Overwrite rep */ - for (i = 0; i < ZSTD_REP_NUM; i++) - seqStorePtr->rep[i] = repToConfirm[i]; - - ZSTD_ldm_limitTableUpdate(cctx, anchor); - ZSTD_ldm_fillFastTables(cctx, anchor); - - lastLiterals = blockCompressor(cctx, anchor, iend - anchor); - cctx->nextToUpdate = (U32)(iend - base); - - /* Restore seqStorePtr->rep */ - for (i = 0; i < ZSTD_REP_NUM; i++) - seqStorePtr->rep[i] = savedRep[i]; - - /* Return the last literals size */ - return lastLiterals; -} - -static size_t ZSTD_compressBlock_ldm(ZSTD_CCtx* ctx, - const void* src, size_t srcSize) -{ - return ZSTD_compressBlock_ldm_generic(ctx, src, srcSize); -} - -static size_t ZSTD_compressBlock_ldm_extDict_generic( - ZSTD_CCtx* ctx, - const void* src, size_t srcSize) -{ - ldmState_t* const ldmState = &(ctx->ldmState); - const ldmParams_t ldmParams = ctx->appliedParams.ldmParams; - const U64 hashPower = ldmState->hashPower; - const U32 hBits = ldmParams.hashLog - ldmParams.bucketSizeLog; - const U32 ldmBucketSize = (1 << ldmParams.bucketSizeLog); - const U32 ldmTagMask = (1 << ldmParams.hashEveryLog) - 1; - seqStore_t* const seqStorePtr = &(ctx->seqStore); - const BYTE* const base = ctx->base; - const BYTE* const dictBase = ctx->dictBase; - const BYTE* const istart = (const BYTE*)src; - const BYTE* ip = istart; - const BYTE* anchor = istart; - const U32 lowestIndex = ctx->lowLimit; - const BYTE* const dictStart = dictBase + lowestIndex; - const U32 dictLimit = ctx->dictLimit; - const BYTE* const lowPrefixPtr = base + dictLimit; - const BYTE* const dictEnd = dictBase + dictLimit; - const BYTE* const iend = istart + srcSize; - const BYTE* const ilimit = iend - ldmParams.minMatchLength; - - const ZSTD_blockCompressor blockCompressor = - ZSTD_selectBlockCompressor(ctx->appliedParams.cParams.strategy, 1); - U32* const repToConfirm = seqStorePtr->repToConfirm; - U32 savedRep[ZSTD_REP_NUM]; - U64 rollingHash = 0; - const BYTE* lastHashed = NULL; - size_t i, lastLiterals; - - /* Save seqStorePtr->rep and copy repToConfirm */ - for (i = 0; i < ZSTD_REP_NUM; i++) { - savedRep[i] = repToConfirm[i] = seqStorePtr->rep[i]; - } - - /* Search Loop */ - while (ip < ilimit) { /* < instead of <=, because (ip+1) */ - size_t mLength; - const U32 current = (U32)(ip-base); - size_t forwardMatchLength = 0, backwardMatchLength = 0; - ldmEntry_t* bestEntry = NULL; - if (ip != istart) { - rollingHash = ZSTD_ldm_updateHash(rollingHash, lastHashed[0], - lastHashed[ldmParams.minMatchLength], - hashPower); - } else { - rollingHash = ZSTD_ldm_getRollingHash(ip, ldmParams.minMatchLength); - } - lastHashed = ip; - - if (ZSTD_ldm_getTag(rollingHash, hBits, ldmParams.hashEveryLog) != - ldmTagMask) { - /* Don't insert and don't look for a match */ - ip++; - continue; - } - - /* Get the best entry and compute the match lengths */ - { - ldmEntry_t* const bucket = - ZSTD_ldm_getBucket(ldmState, - ZSTD_ldm_getSmallHash(rollingHash, hBits), - ldmParams); - ldmEntry_t* cur; - size_t bestMatchLength = 0; - U32 const checksum = ZSTD_ldm_getChecksum(rollingHash, hBits); - - for (cur = bucket; cur < bucket + ldmBucketSize; ++cur) { - const BYTE* const curMatchBase = - cur->offset < dictLimit ? dictBase : base; - const BYTE* const pMatch = curMatchBase + cur->offset; - const BYTE* const matchEnd = - cur->offset < dictLimit ? dictEnd : iend; - const BYTE* const lowMatchPtr = - cur->offset < dictLimit ? dictStart : lowPrefixPtr; - size_t curForwardMatchLength, curBackwardMatchLength, - curTotalMatchLength; - - if (cur->checksum != checksum || cur->offset <= lowestIndex) { - continue; - } - - curForwardMatchLength = ZSTD_count_2segments( - ip, pMatch, iend, - matchEnd, lowPrefixPtr); - if (curForwardMatchLength < ldmParams.minMatchLength) { - continue; - } - curBackwardMatchLength = ZSTD_ldm_countBackwardsMatch( - ip, anchor, pMatch, lowMatchPtr); - curTotalMatchLength = curForwardMatchLength + - curBackwardMatchLength; - - if (curTotalMatchLength > bestMatchLength) { - bestMatchLength = curTotalMatchLength; - forwardMatchLength = curForwardMatchLength; - backwardMatchLength = curBackwardMatchLength; - bestEntry = cur; - } - } - } - - /* No match found -- continue searching */ - if (bestEntry == NULL) { - ZSTD_ldm_makeEntryAndInsertByTag(ldmState, rollingHash, hBits, - (U32)(lastHashed - base), - ldmParams); - ip++; - continue; - } - - /* Match found */ - mLength = forwardMatchLength + backwardMatchLength; - ip -= backwardMatchLength; - - /* Call the block compressor on the remaining literals */ - { - /* ip = current - backwardMatchLength - * The match is at (bestEntry->offset - backwardMatchLength) */ - U32 const matchIndex = bestEntry->offset; - U32 const offset = current - matchIndex; - - /* Overwrite rep codes */ - for (i = 0; i < ZSTD_REP_NUM; i++) - seqStorePtr->rep[i] = repToConfirm[i]; - - /* Fill the hash table for the block compressor */ - ZSTD_ldm_limitTableUpdate(ctx, anchor); - ZSTD_ldm_fillFastTables(ctx, anchor); - - /* Call block compressor and get remaining literals */ - lastLiterals = blockCompressor(ctx, anchor, ip - anchor); - ctx->nextToUpdate = (U32)(ip - base); - - /* Update repToConfirm with the new offset */ - for (i = ZSTD_REP_NUM - 1; i > 0; i--) - repToConfirm[i] = repToConfirm[i-1]; - repToConfirm[0] = offset; - - /* Store the sequence with the leftover literals */ - ZSTD_storeSeq(seqStorePtr, lastLiterals, ip - lastLiterals, - offset + ZSTD_REP_MOVE, mLength - MINMATCH); - } - - /* Insert the current entry into the hash table */ - ZSTD_ldm_makeEntryAndInsertByTag(ldmState, rollingHash, hBits, - (U32)(lastHashed - base), - ldmParams); - - /* Fill the hash table from lastHashed+1 to ip+mLength */ - assert(ip + backwardMatchLength == lastHashed); - if (ip + mLength < ilimit) { - rollingHash = ZSTD_ldm_fillLdmHashTable( - ldmState, rollingHash, lastHashed, - ip + mLength, base, hBits, - ldmParams); - lastHashed = ip + mLength - 1; - } - ip += mLength; - anchor = ip; - - /* check immediate repcode */ - while (ip < ilimit) { - U32 const current2 = (U32)(ip-base); - U32 const repIndex2 = current2 - repToConfirm[1]; - const BYTE* repMatch2 = repIndex2 < dictLimit ? - dictBase + repIndex2 : base + repIndex2; - if ( (((U32)((dictLimit-1) - repIndex2) >= 3) & - (repIndex2 > lowestIndex)) /* intentional overflow */ - && (MEM_read32(repMatch2) == MEM_read32(ip)) ) { - const BYTE* const repEnd2 = repIndex2 < dictLimit ? - dictEnd : iend; - size_t const repLength2 = - ZSTD_count_2segments(ip+4, repMatch2+4, iend, - repEnd2, lowPrefixPtr) + 4; - - U32 tmpOffset = repToConfirm[1]; - repToConfirm[1] = repToConfirm[0]; - repToConfirm[0] = tmpOffset; - - ZSTD_storeSeq(seqStorePtr, 0, anchor, 0, repLength2-MINMATCH); - - /* Fill the hash table from lastHashed+1 to ip+repLength2*/ - if (ip + repLength2 < ilimit) { - rollingHash = ZSTD_ldm_fillLdmHashTable( - ldmState, rollingHash, lastHashed, - ip + repLength2, base, hBits, - ldmParams); - lastHashed = ip + repLength2 - 1; - } - ip += repLength2; - anchor = ip; - continue; - } - break; - } - } - - /* Overwrite rep */ - for (i = 0; i < ZSTD_REP_NUM; i++) - seqStorePtr->rep[i] = repToConfirm[i]; - - ZSTD_ldm_limitTableUpdate(ctx, anchor); - ZSTD_ldm_fillFastTables(ctx, anchor); - - /* Call the block compressor one last time on the last literals */ - lastLiterals = blockCompressor(ctx, anchor, iend - anchor); - ctx->nextToUpdate = (U32)(iend - base); - - /* Restore seqStorePtr->rep */ - for (i = 0; i < ZSTD_REP_NUM; i++) - seqStorePtr->rep[i] = savedRep[i]; - - /* Return the last literals size */ - return lastLiterals; -} - -static size_t ZSTD_compressBlock_ldm_extDict(ZSTD_CCtx* ctx, - const void* src, size_t srcSize) -{ - return ZSTD_compressBlock_ldm_extDict_generic(ctx, src, srcSize); -} - static void ZSTD_storeLastLiterals(seqStore_t* seqStorePtr, const BYTE* anchor, size_t lastLLSize) { diff --git a/lib/compress/zstd_ldm.c b/lib/compress/zstd_ldm.c new file mode 100644 index 00000000..4b6d0871 --- /dev/null +++ b/lib/compress/zstd_ldm.c @@ -0,0 +1,702 @@ +/* + * Copyright (c) 2016-present, Yann Collet, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + */ + +#include "zstd_ldm.h" + +#include "zstd_fast.h" /* ZSTD_fillHashTable() */ +#include "zstd_double_fast.h" /* ZSTD_fillDoubleHashTable() */ + +#define LDM_BUCKET_SIZE_LOG 3 +#define LDM_MIN_MATCH_LENGTH 64 +#define LDM_HASH_LOG 20 +#define LDM_HASH_CHAR_OFFSET 10 + +size_t ZSTD_ldm_initializeParameters(ldmParams_t* params, U32 enableLdm) +{ + ZSTD_STATIC_ASSERT(LDM_BUCKET_SIZE_LOG <= ZSTD_LDM_BUCKETSIZELOG_MAX); + params->enableLdm = enableLdm>0; + params->hashLog = LDM_HASH_LOG; + params->bucketSizeLog = LDM_BUCKET_SIZE_LOG; + params->minMatchLength = LDM_MIN_MATCH_LENGTH; + params->hashEveryLog = ZSTD_LDM_HASHEVERYLOG_NOTSET; + return 0; +} + +void ZSTD_ldm_adjustParameters(ldmParams_t* params, U32 windowLog) +{ + if (params->hashEveryLog == ZSTD_LDM_HASHEVERYLOG_NOTSET) { + params->hashEveryLog = + windowLog < params->hashLog ? 0 : windowLog - params->hashLog; + } + params->bucketSizeLog = MIN(params->bucketSizeLog, params->hashLog); +} + +size_t ZSTD_ldm_getTableSize(U32 hashLog, U32 bucketSizeLog) { + size_t const ldmHSize = ((size_t)1) << hashLog; + size_t const ldmBucketSizeLog = MIN(bucketSizeLog, hashLog); + size_t const ldmBucketSize = + ((size_t)1) << (hashLog - ldmBucketSizeLog); + return ldmBucketSize + (ldmHSize * (sizeof(ldmEntry_t))); +} + +/** ZSTD_ldm_getSmallHash() : + * numBits should be <= 32 + * If numBits==0, returns 0. + * @return : the most significant numBits of value. */ +static U32 ZSTD_ldm_getSmallHash(U64 value, U32 numBits) +{ + assert(numBits <= 32); + return numBits == 0 ? 0 : (U32)(value >> (64 - numBits)); +} + +/** ZSTD_ldm_getChecksum() : + * numBitsToDiscard should be <= 32 + * @return : the next most significant 32 bits after numBitsToDiscard */ +static U32 ZSTD_ldm_getChecksum(U64 hash, U32 numBitsToDiscard) +{ + assert(numBitsToDiscard <= 32); + return (hash >> (64 - 32 - numBitsToDiscard)) & 0xFFFFFFFF; +} + +/** ZSTD_ldm_getTag() ; + * Given the hash, returns the most significant numTagBits bits + * after (32 + hbits) bits. + * + * If there are not enough bits remaining, return the last + * numTagBits bits. */ +static U32 ZSTD_ldm_getTag(U64 hash, U32 hbits, U32 numTagBits) +{ + assert(numTagBits <= 32 && hbits <= 32); + if (32 - hbits < numTagBits) { + return hash & ((1 << numTagBits) - 1); + } else { + return (hash >> (32 - hbits - numTagBits)) & ((1 << numTagBits) - 1); + } +} + +/** ZSTD_ldm_getBucket() : + * Returns a pointer to the start of the bucket associated with hash. */ +static ldmEntry_t* ZSTD_ldm_getBucket( + ldmState_t* ldmState, size_t hash, ldmParams_t const ldmParams) +{ + return ldmState->hashTable + (hash << ldmParams.bucketSizeLog); +} + +/** ZSTD_ldm_insertEntry() : + * Insert the entry with corresponding hash into the hash table */ +static void ZSTD_ldm_insertEntry(ldmState_t* ldmState, + size_t const hash, const ldmEntry_t entry, + ldmParams_t const ldmParams) +{ + BYTE* const bucketOffsets = ldmState->bucketOffsets; + *(ZSTD_ldm_getBucket(ldmState, hash, ldmParams) + bucketOffsets[hash]) = entry; + bucketOffsets[hash]++; + bucketOffsets[hash] &= (1 << ldmParams.bucketSizeLog) - 1; +} + +/** ZSTD_ldm_makeEntryAndInsertByTag() : + * + * Gets the small hash, checksum, and tag from the rollingHash. + * + * If the tag matches (1 << ldmParams.hashEveryLog)-1, then + * creates an ldmEntry from the offset, and inserts it into the hash table. + * + * hBits is the length of the small hash, which is the most significant hBits + * of rollingHash. The checksum is the next 32 most significant bits, followed + * by ldmParams.hashEveryLog bits that make up the tag. */ +static void ZSTD_ldm_makeEntryAndInsertByTag(ldmState_t* ldmState, + U64 const rollingHash, + U32 const hBits, + U32 const offset, + ldmParams_t const ldmParams) +{ + U32 const tag = ZSTD_ldm_getTag(rollingHash, hBits, ldmParams.hashEveryLog); + U32 const tagMask = (1 << ldmParams.hashEveryLog) - 1; + if (tag == tagMask) { + U32 const hash = ZSTD_ldm_getSmallHash(rollingHash, hBits); + U32 const checksum = ZSTD_ldm_getChecksum(rollingHash, hBits); + ldmEntry_t entry; + entry.offset = offset; + entry.checksum = checksum; + ZSTD_ldm_insertEntry(ldmState, hash, entry, ldmParams); + } +} + +/** ZSTD_ldm_getRollingHash() : + * Get a 64-bit hash using the first len bytes from buf. + * + * Giving bytes s = s_1, s_2, ... s_k, the hash is defined to be + * H(s) = s_1*(a^(k-1)) + s_2*(a^(k-2)) + ... + s_k*(a^0) + * + * where the constant a is defined to be prime8bytes. + * + * The implementation adds an offset to each byte, so + * H(s) = (s_1 + HASH_CHAR_OFFSET)*(a^(k-1)) + ... */ +static U64 ZSTD_ldm_getRollingHash(const BYTE* buf, U32 len) +{ + U64 ret = 0; + U32 i; + for (i = 0; i < len; i++) { + ret *= prime8bytes; + ret += buf[i] + LDM_HASH_CHAR_OFFSET; + } + return ret; +} + +/** ZSTD_ldm_ipow() : + * Return base^exp. */ +static U64 ZSTD_ldm_ipow(U64 base, U64 exp) +{ + U64 ret = 1; + while (exp) { + if (exp & 1) { ret *= base; } + exp >>= 1; + base *= base; + } + return ret; +} + +U64 ZSTD_ldm_getHashPower(U32 minMatchLength) { + assert(minMatchLength >= ZSTD_LDM_MINMATCH_MIN); + return ZSTD_ldm_ipow(prime8bytes, minMatchLength - 1); +} + +/** ZSTD_ldm_updateHash() : + * Updates hash by removing toRemove and adding toAdd. */ +static U64 ZSTD_ldm_updateHash(U64 hash, BYTE toRemove, BYTE toAdd, U64 hashPower) +{ + hash -= ((toRemove + LDM_HASH_CHAR_OFFSET) * hashPower); + hash *= prime8bytes; + hash += toAdd + LDM_HASH_CHAR_OFFSET; + return hash; +} + +/** ZSTD_ldm_countBackwardsMatch() : + * Returns the number of bytes that match backwards before pIn and pMatch. + * + * We count only bytes where pMatch >= pBase and pIn >= pAnchor. */ +static size_t ZSTD_ldm_countBackwardsMatch( + const BYTE* pIn, const BYTE* pAnchor, + const BYTE* pMatch, const BYTE* pBase) +{ + size_t matchLength = 0; + while (pIn > pAnchor && pMatch > pBase && pIn[-1] == pMatch[-1]) { + pIn--; + pMatch--; + matchLength++; + } + return matchLength; +} + +/** ZSTD_ldm_fillFastTables() : + * + * Fills the relevant tables for the ZSTD_fast and ZSTD_dfast strategies. + * This is similar to ZSTD_loadDictionaryContent. + * + * The tables for the other strategies are filled within their + * block compressors. */ +static size_t ZSTD_ldm_fillFastTables(ZSTD_CCtx* zc, const void* end) +{ + const BYTE* const iend = (const BYTE*)end; + const U32 mls = zc->appliedParams.cParams.searchLength; + + switch(zc->appliedParams.cParams.strategy) + { + case ZSTD_fast: + ZSTD_fillHashTable(zc, iend, mls); + zc->nextToUpdate = (U32)(iend - zc->base); + break; + + case ZSTD_dfast: + ZSTD_fillDoubleHashTable(zc, iend, mls); + zc->nextToUpdate = (U32)(iend - zc->base); + break; + + case ZSTD_greedy: + case ZSTD_lazy: + case ZSTD_lazy2: + case ZSTD_btlazy2: + case ZSTD_btopt: + case ZSTD_btultra: + break; + default: + assert(0); /* not possible : not a valid strategy id */ + } + + return 0; +} + +/** ZSTD_ldm_fillLdmHashTable() : + * + * Fills hashTable from (lastHashed + 1) to iend (non-inclusive). + * lastHash is the rolling hash that corresponds to lastHashed. + * + * Returns the rolling hash corresponding to position iend-1. */ +static U64 ZSTD_ldm_fillLdmHashTable(ldmState_t* state, + U64 lastHash, const BYTE* lastHashed, + const BYTE* iend, const BYTE* base, + U32 hBits, ldmParams_t const ldmParams) +{ + U64 rollingHash = lastHash; + const BYTE* cur = lastHashed + 1; + + while (cur < iend) { + rollingHash = ZSTD_ldm_updateHash(rollingHash, cur[-1], + cur[ldmParams.minMatchLength-1], + state->hashPower); + ZSTD_ldm_makeEntryAndInsertByTag(state, + rollingHash, hBits, + (U32)(cur - base), ldmParams); + ++cur; + } + return rollingHash; +} + + +/** ZSTD_ldm_limitTableUpdate() : + * + * Sets cctx->nextToUpdate to a position corresponding closer to anchor + * if it is far way + * (after a long match, only update tables a limited amount). */ +static void ZSTD_ldm_limitTableUpdate(ZSTD_CCtx* cctx, const BYTE* anchor) +{ + U32 const current = (U32)(anchor - cctx->base); + if (current > cctx->nextToUpdate + 1024) { + cctx->nextToUpdate = + current - MIN(512, current - cctx->nextToUpdate - 1024); + } +} + +typedef size_t (*ZSTD_blockCompressor) (ZSTD_CCtx* ctx, const void* src, size_t srcSize); +/* defined in zstd_compress.c */ +ZSTD_blockCompressor ZSTD_selectBlockCompressor(ZSTD_strategy strat, int extDict); + +FORCE_INLINE_TEMPLATE +size_t ZSTD_compressBlock_ldm_generic(ZSTD_CCtx* cctx, + const void* src, size_t srcSize) +{ + ldmState_t* const ldmState = &(cctx->ldmState); + const ldmParams_t ldmParams = cctx->appliedParams.ldmParams; + const U64 hashPower = ldmState->hashPower; + const U32 hBits = ldmParams.hashLog - ldmParams.bucketSizeLog; + const U32 ldmBucketSize = (1 << ldmParams.bucketSizeLog); + const U32 ldmTagMask = (1 << ldmParams.hashEveryLog) - 1; + seqStore_t* const seqStorePtr = &(cctx->seqStore); + const BYTE* const base = cctx->base; + const BYTE* const istart = (const BYTE*)src; + const BYTE* ip = istart; + const BYTE* anchor = istart; + const U32 lowestIndex = cctx->dictLimit; + const BYTE* const lowest = base + lowestIndex; + const BYTE* const iend = istart + srcSize; + const BYTE* const ilimit = iend - ldmParams.minMatchLength; + + const ZSTD_blockCompressor blockCompressor = + ZSTD_selectBlockCompressor(cctx->appliedParams.cParams.strategy, 0); + U32* const repToConfirm = seqStorePtr->repToConfirm; + U32 savedRep[ZSTD_REP_NUM]; + U64 rollingHash = 0; + const BYTE* lastHashed = NULL; + size_t i, lastLiterals; + + /* Save seqStorePtr->rep and copy repToConfirm */ + for (i = 0; i < ZSTD_REP_NUM; i++) + savedRep[i] = repToConfirm[i] = seqStorePtr->rep[i]; + + /* Main Search Loop */ + while (ip < ilimit) { /* < instead of <=, because repcode check at (ip+1) */ + size_t mLength; + U32 const current = (U32)(ip - base); + size_t forwardMatchLength = 0, backwardMatchLength = 0; + ldmEntry_t* bestEntry = NULL; + if (ip != istart) { + rollingHash = ZSTD_ldm_updateHash(rollingHash, lastHashed[0], + lastHashed[ldmParams.minMatchLength], + hashPower); + } else { + rollingHash = ZSTD_ldm_getRollingHash(ip, ldmParams.minMatchLength); + } + lastHashed = ip; + + /* Do not insert and do not look for a match */ + if (ZSTD_ldm_getTag(rollingHash, hBits, ldmParams.hashEveryLog) != + ldmTagMask) { + ip++; + continue; + } + + /* Get the best entry and compute the match lengths */ + { + ldmEntry_t* const bucket = + ZSTD_ldm_getBucket(ldmState, + ZSTD_ldm_getSmallHash(rollingHash, hBits), + ldmParams); + ldmEntry_t* cur; + size_t bestMatchLength = 0; + U32 const checksum = ZSTD_ldm_getChecksum(rollingHash, hBits); + + for (cur = bucket; cur < bucket + ldmBucketSize; ++cur) { + const BYTE* const pMatch = cur->offset + base; + size_t curForwardMatchLength, curBackwardMatchLength, + curTotalMatchLength; + if (cur->checksum != checksum || cur->offset <= lowestIndex) { + continue; + } + + curForwardMatchLength = ZSTD_count(ip, pMatch, iend); + if (curForwardMatchLength < ldmParams.minMatchLength) { + continue; + } + curBackwardMatchLength = ZSTD_ldm_countBackwardsMatch( + ip, anchor, pMatch, lowest); + curTotalMatchLength = curForwardMatchLength + + curBackwardMatchLength; + + if (curTotalMatchLength > bestMatchLength) { + bestMatchLength = curTotalMatchLength; + forwardMatchLength = curForwardMatchLength; + backwardMatchLength = curBackwardMatchLength; + bestEntry = cur; + } + } + } + + /* No match found -- continue searching */ + if (bestEntry == NULL) { + ZSTD_ldm_makeEntryAndInsertByTag(ldmState, rollingHash, + hBits, current, + ldmParams); + ip++; + continue; + } + + /* Match found */ + mLength = forwardMatchLength + backwardMatchLength; + ip -= backwardMatchLength; + + /* Call the block compressor on the remaining literals */ + { + U32 const matchIndex = bestEntry->offset; + const BYTE* const match = base + matchIndex - backwardMatchLength; + U32 const offset = (U32)(ip - match); + + /* Overwrite rep codes */ + for (i = 0; i < ZSTD_REP_NUM; i++) + seqStorePtr->rep[i] = repToConfirm[i]; + + /* Fill tables for block compressor */ + ZSTD_ldm_limitTableUpdate(cctx, anchor); + ZSTD_ldm_fillFastTables(cctx, anchor); + + /* Call block compressor and get remaining literals */ + lastLiterals = blockCompressor(cctx, anchor, ip - anchor); + cctx->nextToUpdate = (U32)(ip - base); + + /* Update repToConfirm with the new offset */ + for (i = ZSTD_REP_NUM - 1; i > 0; i--) + repToConfirm[i] = repToConfirm[i-1]; + repToConfirm[0] = offset; + + /* Store the sequence with the leftover literals */ + ZSTD_storeSeq(seqStorePtr, lastLiterals, ip - lastLiterals, + offset + ZSTD_REP_MOVE, mLength - MINMATCH); + } + + /* Insert the current entry into the hash table */ + ZSTD_ldm_makeEntryAndInsertByTag(ldmState, rollingHash, hBits, + (U32)(lastHashed - base), + ldmParams); + + assert(ip + backwardMatchLength == lastHashed); + + /* Fill the hash table from lastHashed+1 to ip+mLength*/ + /* Heuristic: don't need to fill the entire table at end of block */ + if (ip + mLength < ilimit) { + rollingHash = ZSTD_ldm_fillLdmHashTable( + ldmState, rollingHash, lastHashed, + ip + mLength, base, hBits, ldmParams); + lastHashed = ip + mLength - 1; + } + ip += mLength; + anchor = ip; + /* Check immediate repcode */ + while ( (ip < ilimit) + && ( (repToConfirm[1] > 0) && (repToConfirm[1] <= (U32)(ip-lowest)) + && (MEM_read32(ip) == MEM_read32(ip - repToConfirm[1])) )) { + + size_t const rLength = ZSTD_count(ip+4, ip+4-repToConfirm[1], + iend) + 4; + /* Swap repToConfirm[1] <=> repToConfirm[0] */ + { + U32 const tmpOff = repToConfirm[1]; + repToConfirm[1] = repToConfirm[0]; + repToConfirm[0] = tmpOff; + } + + ZSTD_storeSeq(seqStorePtr, 0, anchor, 0, rLength-MINMATCH); + + /* Fill the hash table from lastHashed+1 to ip+rLength*/ + if (ip + rLength < ilimit) { + rollingHash = ZSTD_ldm_fillLdmHashTable( + ldmState, rollingHash, lastHashed, + ip + rLength, base, hBits, ldmParams); + lastHashed = ip + rLength - 1; + } + ip += rLength; + anchor = ip; + } + } + + /* Overwrite rep */ + for (i = 0; i < ZSTD_REP_NUM; i++) + seqStorePtr->rep[i] = repToConfirm[i]; + + ZSTD_ldm_limitTableUpdate(cctx, anchor); + ZSTD_ldm_fillFastTables(cctx, anchor); + + lastLiterals = blockCompressor(cctx, anchor, iend - anchor); + cctx->nextToUpdate = (U32)(iend - base); + + /* Restore seqStorePtr->rep */ + for (i = 0; i < ZSTD_REP_NUM; i++) + seqStorePtr->rep[i] = savedRep[i]; + + /* Return the last literals size */ + return lastLiterals; +} + +size_t ZSTD_compressBlock_ldm(ZSTD_CCtx* ctx, const void* src, size_t srcSize) +{ + return ZSTD_compressBlock_ldm_generic(ctx, src, srcSize); +} + +static size_t ZSTD_compressBlock_ldm_extDict_generic( + ZSTD_CCtx* ctx, + const void* src, size_t srcSize) +{ + ldmState_t* const ldmState = &(ctx->ldmState); + const ldmParams_t ldmParams = ctx->appliedParams.ldmParams; + const U64 hashPower = ldmState->hashPower; + const U32 hBits = ldmParams.hashLog - ldmParams.bucketSizeLog; + const U32 ldmBucketSize = (1 << ldmParams.bucketSizeLog); + const U32 ldmTagMask = (1 << ldmParams.hashEveryLog) - 1; + seqStore_t* const seqStorePtr = &(ctx->seqStore); + const BYTE* const base = ctx->base; + const BYTE* const dictBase = ctx->dictBase; + const BYTE* const istart = (const BYTE*)src; + const BYTE* ip = istart; + const BYTE* anchor = istart; + const U32 lowestIndex = ctx->lowLimit; + const BYTE* const dictStart = dictBase + lowestIndex; + const U32 dictLimit = ctx->dictLimit; + const BYTE* const lowPrefixPtr = base + dictLimit; + const BYTE* const dictEnd = dictBase + dictLimit; + const BYTE* const iend = istart + srcSize; + const BYTE* const ilimit = iend - ldmParams.minMatchLength; + + const ZSTD_blockCompressor blockCompressor = + ZSTD_selectBlockCompressor(ctx->appliedParams.cParams.strategy, 1); + U32* const repToConfirm = seqStorePtr->repToConfirm; + U32 savedRep[ZSTD_REP_NUM]; + U64 rollingHash = 0; + const BYTE* lastHashed = NULL; + size_t i, lastLiterals; + + /* Save seqStorePtr->rep and copy repToConfirm */ + for (i = 0; i < ZSTD_REP_NUM; i++) { + savedRep[i] = repToConfirm[i] = seqStorePtr->rep[i]; + } + + /* Search Loop */ + while (ip < ilimit) { /* < instead of <=, because (ip+1) */ + size_t mLength; + const U32 current = (U32)(ip-base); + size_t forwardMatchLength = 0, backwardMatchLength = 0; + ldmEntry_t* bestEntry = NULL; + if (ip != istart) { + rollingHash = ZSTD_ldm_updateHash(rollingHash, lastHashed[0], + lastHashed[ldmParams.minMatchLength], + hashPower); + } else { + rollingHash = ZSTD_ldm_getRollingHash(ip, ldmParams.minMatchLength); + } + lastHashed = ip; + + if (ZSTD_ldm_getTag(rollingHash, hBits, ldmParams.hashEveryLog) != + ldmTagMask) { + /* Don't insert and don't look for a match */ + ip++; + continue; + } + + /* Get the best entry and compute the match lengths */ + { + ldmEntry_t* const bucket = + ZSTD_ldm_getBucket(ldmState, + ZSTD_ldm_getSmallHash(rollingHash, hBits), + ldmParams); + ldmEntry_t* cur; + size_t bestMatchLength = 0; + U32 const checksum = ZSTD_ldm_getChecksum(rollingHash, hBits); + + for (cur = bucket; cur < bucket + ldmBucketSize; ++cur) { + const BYTE* const curMatchBase = + cur->offset < dictLimit ? dictBase : base; + const BYTE* const pMatch = curMatchBase + cur->offset; + const BYTE* const matchEnd = + cur->offset < dictLimit ? dictEnd : iend; + const BYTE* const lowMatchPtr = + cur->offset < dictLimit ? dictStart : lowPrefixPtr; + size_t curForwardMatchLength, curBackwardMatchLength, + curTotalMatchLength; + + if (cur->checksum != checksum || cur->offset <= lowestIndex) { + continue; + } + + curForwardMatchLength = ZSTD_count_2segments( + ip, pMatch, iend, + matchEnd, lowPrefixPtr); + if (curForwardMatchLength < ldmParams.minMatchLength) { + continue; + } + curBackwardMatchLength = ZSTD_ldm_countBackwardsMatch( + ip, anchor, pMatch, lowMatchPtr); + curTotalMatchLength = curForwardMatchLength + + curBackwardMatchLength; + + if (curTotalMatchLength > bestMatchLength) { + bestMatchLength = curTotalMatchLength; + forwardMatchLength = curForwardMatchLength; + backwardMatchLength = curBackwardMatchLength; + bestEntry = cur; + } + } + } + + /* No match found -- continue searching */ + if (bestEntry == NULL) { + ZSTD_ldm_makeEntryAndInsertByTag(ldmState, rollingHash, hBits, + (U32)(lastHashed - base), + ldmParams); + ip++; + continue; + } + + /* Match found */ + mLength = forwardMatchLength + backwardMatchLength; + ip -= backwardMatchLength; + + /* Call the block compressor on the remaining literals */ + { + /* ip = current - backwardMatchLength + * The match is at (bestEntry->offset - backwardMatchLength) */ + U32 const matchIndex = bestEntry->offset; + U32 const offset = current - matchIndex; + + /* Overwrite rep codes */ + for (i = 0; i < ZSTD_REP_NUM; i++) + seqStorePtr->rep[i] = repToConfirm[i]; + + /* Fill the hash table for the block compressor */ + ZSTD_ldm_limitTableUpdate(ctx, anchor); + ZSTD_ldm_fillFastTables(ctx, anchor); + + /* Call block compressor and get remaining literals */ + lastLiterals = blockCompressor(ctx, anchor, ip - anchor); + ctx->nextToUpdate = (U32)(ip - base); + + /* Update repToConfirm with the new offset */ + for (i = ZSTD_REP_NUM - 1; i > 0; i--) + repToConfirm[i] = repToConfirm[i-1]; + repToConfirm[0] = offset; + + /* Store the sequence with the leftover literals */ + ZSTD_storeSeq(seqStorePtr, lastLiterals, ip - lastLiterals, + offset + ZSTD_REP_MOVE, mLength - MINMATCH); + } + + /* Insert the current entry into the hash table */ + ZSTD_ldm_makeEntryAndInsertByTag(ldmState, rollingHash, hBits, + (U32)(lastHashed - base), + ldmParams); + + /* Fill the hash table from lastHashed+1 to ip+mLength */ + assert(ip + backwardMatchLength == lastHashed); + if (ip + mLength < ilimit) { + rollingHash = ZSTD_ldm_fillLdmHashTable( + ldmState, rollingHash, lastHashed, + ip + mLength, base, hBits, + ldmParams); + lastHashed = ip + mLength - 1; + } + ip += mLength; + anchor = ip; + + /* check immediate repcode */ + while (ip < ilimit) { + U32 const current2 = (U32)(ip-base); + U32 const repIndex2 = current2 - repToConfirm[1]; + const BYTE* repMatch2 = repIndex2 < dictLimit ? + dictBase + repIndex2 : base + repIndex2; + if ( (((U32)((dictLimit-1) - repIndex2) >= 3) & + (repIndex2 > lowestIndex)) /* intentional overflow */ + && (MEM_read32(repMatch2) == MEM_read32(ip)) ) { + const BYTE* const repEnd2 = repIndex2 < dictLimit ? + dictEnd : iend; + size_t const repLength2 = + ZSTD_count_2segments(ip+4, repMatch2+4, iend, + repEnd2, lowPrefixPtr) + 4; + + U32 tmpOffset = repToConfirm[1]; + repToConfirm[1] = repToConfirm[0]; + repToConfirm[0] = tmpOffset; + + ZSTD_storeSeq(seqStorePtr, 0, anchor, 0, repLength2-MINMATCH); + + /* Fill the hash table from lastHashed+1 to ip+repLength2*/ + if (ip + repLength2 < ilimit) { + rollingHash = ZSTD_ldm_fillLdmHashTable( + ldmState, rollingHash, lastHashed, + ip + repLength2, base, hBits, + ldmParams); + lastHashed = ip + repLength2 - 1; + } + ip += repLength2; + anchor = ip; + continue; + } + break; + } + } + + /* Overwrite rep */ + for (i = 0; i < ZSTD_REP_NUM; i++) + seqStorePtr->rep[i] = repToConfirm[i]; + + ZSTD_ldm_limitTableUpdate(ctx, anchor); + ZSTD_ldm_fillFastTables(ctx, anchor); + + /* Call the block compressor one last time on the last literals */ + lastLiterals = blockCompressor(ctx, anchor, iend - anchor); + ctx->nextToUpdate = (U32)(iend - base); + + /* Restore seqStorePtr->rep */ + for (i = 0; i < ZSTD_REP_NUM; i++) + seqStorePtr->rep[i] = savedRep[i]; + + /* Return the last literals size */ + return lastLiterals; +} + +size_t ZSTD_compressBlock_ldm_extDict(ZSTD_CCtx* ctx, + const void* src, size_t srcSize) +{ + return ZSTD_compressBlock_ldm_extDict_generic(ctx, src, srcSize); +} diff --git a/lib/compress/zstd_ldm.h b/lib/compress/zstd_ldm.h new file mode 100644 index 00000000..7a624839 --- /dev/null +++ b/lib/compress/zstd_ldm.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2016-present, Yann Collet, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + */ + +#ifndef ZSTD_LDM_H +#define ZSTD_LDM_H + +#include "zstd_compress.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +/*-************************************* +* Long distance matching +***************************************/ + +#define ZSTD_LDM_WINDOW_LOG 27 +#define ZSTD_LDM_HASHEVERYLOG_NOTSET 9999 + +/** ZSTD_compressBlock_ldm_generic() : + * + * This is a block compressor intended for long distance matching. + * + * The function searches for matches of length at least + * ldmParams.minMatchLength using a hash table in cctx->ldmState. + * Matches can be at a distance of up to cParams.windowLog. + * + * Upon finding a match, the unmatched literals are compressed using a + * ZSTD_blockCompressor (depending on the strategy in the compression + * parameters), which stores the matched sequences. The "long distance" + * match is then stored with the remaining literals from the + * ZSTD_blockCompressor. */ +size_t ZSTD_compressBlock_ldm(ZSTD_CCtx* cctx, const void* src, size_t srcSize); +size_t ZSTD_compressBlock_ldm_extDict(ZSTD_CCtx* ctx, + const void* src, size_t srcSize); + +/** ZSTD_ldm_initializeParameters() : + * Initialize the long distance matching parameters to their default values. */ +size_t ZSTD_ldm_initializeParameters(ldmParams_t* params, U32 enableLdm); + +/** ZSTD_ldm_getTableSize() : + * Estimate the space needed for long distance matching tables. */ +size_t ZSTD_ldm_getTableSize(U32 hashLog, U32 bucketSizeLog); + +/** ZSTD_ldm_getTableSize() : + * Return prime8bytes^(minMatchLength-1) */ +U64 ZSTD_ldm_getHashPower(U32 minMatchLength); + +/** ZSTD_ldm_adjustParameters() : + * If the params->hashEveryLog is not set, set it to its default value based on + * windowLog and params->hashLog. + * + * Ensures that params->bucketSizeLog is <= params->hashLog (setting it to + * params->hashLog if it is not). */ +void ZSTD_ldm_adjustParameters(ldmParams_t* params, U32 windowLog); + +#if defined (__cplusplus) +} +#endif + +#endif /* ZSTD_FAST_H */ From 0d1b54db61e4ca3ed29b5b633561878ead04c196 Mon Sep 17 00:00:00 2001 From: Stella Lau Date: Mon, 11 Sep 2017 13:02:09 -0700 Subject: [PATCH 14/15] Explicitly cast raw numerals when left-shifting --- lib/compress/zstd_compress.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 2364eee2..a2bd2075 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -1151,17 +1151,17 @@ static void ZSTD_ldm_reduceTable(ldmEntry_t* const table, U32 const size, * rescale all indexes to avoid future overflow (indexes are U32) */ static void ZSTD_reduceIndex (ZSTD_CCtx* zc, const U32 reducerValue) { - { U32 const hSize = 1 << zc->appliedParams.cParams.hashLog; + { U32 const hSize = (U32)1 << zc->appliedParams.cParams.hashLog; ZSTD_reduceTable(zc->hashTable, hSize, reducerValue); } - { U32 const chainSize = (zc->appliedParams.cParams.strategy == ZSTD_fast) ? 0 : (1 << zc->appliedParams.cParams.chainLog); + { U32 const chainSize = (zc->appliedParams.cParams.strategy == ZSTD_fast) ? 0 : ((U32)1 << zc->appliedParams.cParams.chainLog); ZSTD_reduceTable(zc->chainTable, chainSize, reducerValue); } - { U32 const h3Size = (zc->hashLog3) ? 1 << zc->hashLog3 : 0; + { U32 const h3Size = (zc->hashLog3) ? (U32)1 << zc->hashLog3 : 0; ZSTD_reduceTable(zc->hashTable3, h3Size, reducerValue); } { if (zc->appliedParams.ldmParams.enableLdm) { - U32 const ldmHSize = 1 << zc->appliedParams.ldmParams.hashLog; + U32 const ldmHSize = (U32)1 << zc->appliedParams.ldmParams.hashLog; ZSTD_ldm_reduceTable(zc->ldmState.hashTable, ldmHSize, reducerValue); } } @@ -3163,11 +3163,11 @@ static U32 ZSTD_ldm_getChecksum(U64 hash, U32 numBitsToDiscard) * numTagBits bits. */ static U32 ZSTD_ldm_getTag(U64 hash, U32 hbits, U32 numTagBits) { - assert(numTagBits <= 32 && hbits <= 32); + assert(numTagBits < 32 && hbits <= 32); if (32 - hbits < numTagBits) { - return hash & ((1 << numTagBits) - 1); + return hash & (((U32)1 << numTagBits) - 1); } else { - return (hash >> (32 - hbits - numTagBits)) & ((1 << numTagBits) - 1); + return (hash >> (32 - hbits - numTagBits)) & (((U32)1 << numTagBits) - 1); } } @@ -3188,7 +3188,7 @@ static void ZSTD_ldm_insertEntry(ldmState_t* ldmState, BYTE* const bucketOffsets = ldmState->bucketOffsets; *(ZSTD_ldm_getBucket(ldmState, hash, ldmParams) + bucketOffsets[hash]) = entry; bucketOffsets[hash]++; - bucketOffsets[hash] &= (1 << ldmParams.bucketSizeLog) - 1; + bucketOffsets[hash] &= ((U32)1 << ldmParams.bucketSizeLog) - 1; } /** ZSTD_ldm_makeEntryAndInsertByTag() : @@ -3208,7 +3208,7 @@ static void ZSTD_ldm_makeEntryAndInsertByTag(ldmState_t* ldmState, ldmParams_t const ldmParams) { U32 const tag = ZSTD_ldm_getTag(rollingHash, hBits, ldmParams.hashEveryLog); - U32 const tagMask = (1 << ldmParams.hashEveryLog) - 1; + U32 const tagMask = ((U32)1 << ldmParams.hashEveryLog) - 1; if (tag == tagMask) { U32 const hash = ZSTD_ldm_getSmallHash(rollingHash, hBits); U32 const checksum = ZSTD_ldm_getChecksum(rollingHash, hBits); @@ -3385,8 +3385,8 @@ size_t ZSTD_compressBlock_ldm_generic(ZSTD_CCtx* cctx, const ldmParams_t ldmParams = cctx->appliedParams.ldmParams; const U64 hashPower = ldmState->hashPower; const U32 hBits = ldmParams.hashLog - ldmParams.bucketSizeLog; - const U32 ldmBucketSize = (1 << ldmParams.bucketSizeLog); - const U32 ldmTagMask = (1 << ldmParams.hashEveryLog) - 1; + const U32 ldmBucketSize = ((U32)1 << ldmParams.bucketSizeLog); + const U32 ldmTagMask = ((U32)1 << ldmParams.hashEveryLog) - 1; seqStore_t* const seqStorePtr = &(cctx->seqStore); const BYTE* const base = cctx->base; const BYTE* const istart = (const BYTE*)src; @@ -3585,8 +3585,8 @@ static size_t ZSTD_compressBlock_ldm_extDict_generic( const ldmParams_t ldmParams = ctx->appliedParams.ldmParams; const U64 hashPower = ldmState->hashPower; const U32 hBits = ldmParams.hashLog - ldmParams.bucketSizeLog; - const U32 ldmBucketSize = (1 << ldmParams.bucketSizeLog); - const U32 ldmTagMask = (1 << ldmParams.hashEveryLog) - 1; + const U32 ldmBucketSize = ((U32)1 << ldmParams.bucketSizeLog); + const U32 ldmTagMask = ((U32)1 << ldmParams.hashEveryLog) - 1; seqStore_t* const seqStorePtr = &(ctx->seqStore); const BYTE* const base = ctx->base; const BYTE* const dictBase = ctx->dictBase; From 3d8e313f64879f4efe91220436bc02da9a95f05b Mon Sep 17 00:00:00 2001 From: Stella Lau Date: Mon, 11 Sep 2017 17:21:28 -0700 Subject: [PATCH 15/15] Reduce ldm hash table size in test --- tests/zstreamtest.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/zstreamtest.c b/tests/zstreamtest.c index d1993d21..f80743e6 100644 --- a/tests/zstreamtest.c +++ b/tests/zstreamtest.c @@ -1390,7 +1390,7 @@ static int fuzzerTests_newAPI(U32 seed, U32 nbTests, unsigned startTest, double /* mess with long distance matching parameters */ if (FUZ_rand(&lseed) & 1) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_enableLongDistanceMatching, FUZ_rand(&lseed) & 63, useOpaqueAPI) ); - if (FUZ_rand(&lseed) & 3) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_ldmHashLog, FUZ_randomClampedLength(&lseed, ZSTD_HASHLOG_MIN, ZSTD_HASHLOG_MAX), useOpaqueAPI) ); + if (FUZ_rand(&lseed) & 3) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_ldmHashLog, FUZ_randomClampedLength(&lseed, ZSTD_HASHLOG_MIN, 23), useOpaqueAPI) ); if (FUZ_rand(&lseed) & 3) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_ldmMinMatch, FUZ_randomClampedLength(&lseed, ZSTD_LDM_MINMATCH_MIN, ZSTD_LDM_MINMATCH_MAX), useOpaqueAPI) ); if (FUZ_rand(&lseed) & 3) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_ldmBucketSizeLog, FUZ_randomClampedLength(&lseed, 0, ZSTD_LDM_BUCKETSIZELOG_MAX), useOpaqueAPI) ); if (FUZ_rand(&lseed) & 3) CHECK_Z( setCCtxParameter(zc, cctxParams, ZSTD_p_ldmHashEveryLog, FUZ_randomClampedLength(&lseed, 0, ZSTD_WINDOWLOG_MAX - ZSTD_HASHLOG_MIN), useOpaqueAPI) );