diff --git a/lib/README.md b/lib/README.md index aa92bd659..dcd888a19 100644 --- a/lib/README.md +++ b/lib/README.md @@ -213,6 +213,11 @@ The file structure is designed to make this selection manually achievable for an For this scenario, it can be set as `ZDICT_QSORT=ZDICT_QSORT_C90`. Other selectable suffixes are `_GNU`, `_APPLE`, `_MSVC` and `_C11`. +- The build macro `ZSTD_LAZY_SKIP_LONG_MATCHES` can be defined for fast lazy + evaluation from compression level 6 to 7. This improves compression performance with + little to no tradeoff for compressibility and decompression performance. + This option is designed for applications with demanding compression speed requirements. + #### Windows : using MinGW+MSYS to create DLL DLL can be created using MinGW+MSYS with the `make libzstd` command. diff --git a/lib/compress/clevels.h b/lib/compress/clevels.h index c18da465f..c61500a4b 100644 --- a/lib/compress/clevels.h +++ b/lib/compress/clevels.h @@ -129,6 +129,32 @@ static const ZSTD_compressionParameters ZSTD_defaultCParameters[4][ZSTD_MAX_CLEV }, }; +#ifdef ZSTD_LAZY_SKIP_LONG_MATCHES +static const ZSTD_compressionParameters ZSTD_CParametersFastCompress[4][2] = { +{ /* "default" - for any srcSize > 256 KB */ + /* W, C, H, S, L, TL, strat */ + /* Larger windows and settings from next levels to improve ratio. + * Accounts for ratio drop from selective lazy evaluation */ + { 23, 19, 20, 4, 5, 8, ZSTD_lazy }, /* level 6 */ + { 23, 19, 20, 4, 5, 16, ZSTD_lazy }, /* level 7 */ +}, +{ /* for srcSize <= 256 KB */ + /* W, C, H, S, L, T, strat */ + { 20, 18, 19, 4, 4, 4, ZSTD_lazy }, /* level 6 */ + { 20, 18, 19, 4, 5, 8, ZSTD_lazy }, /* level 7 */ +}, +{ /* for srcSize <= 128 KB */ + /* W, C, H, S, L, T, strat */ + { 19, 16, 17, 3, 4, 8, ZSTD_lazy }, /* level 6 */ + { 19, 16, 17, 4, 5, 16, ZSTD_lazy }, /* level 7 */ +}, +{ /* for srcSize <= 16 KB */ + /* W, C, H, S, L, T, strat */ + { 14, 14, 14, 4, 4, 8, ZSTD_lazy2 }, /* level 6 */ + { 14, 14, 14, 6, 4, 8, ZSTD_lazy2 }, /* level 7 */ +}, +}; +#endif #endif /* ZSTD_CLEVELS_H */ diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index c06f2e1bb..ac3a0ec7f 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -5268,6 +5268,13 @@ static size_t ZSTD_compressBegin_internal(ZSTD_CCtx* cctx, ZSTD_buffered_policy_e zbuff) { size_t const dictContentSize = cdict ? cdict->dictContentSize : dictSize; +#ifdef ZSTD_LAZY_SKIP_LONG_MATCHES + int compressionLevel = (params->compressionLevel == 0) ? cctx->requestedParams.compressionLevel + : params->compressionLevel; + compressionLevel = MIN(MAX(0, compressionLevel), ZSTD_maxCLevel()); + assert(compressionLevel >= 0 && compressionLevel <= ZSTD_MAX_CLEVEL); + cctx->seqStore.lazyLimit = ZSTD_compressFastLazyLimit[compressionLevel]; +#endif #if ZSTD_TRACE cctx->traceCtx = (ZSTD_trace_compress_begin != NULL) ? ZSTD_trace_compress_begin(cctx) : 0; #endif @@ -8296,7 +8303,14 @@ static ZSTD_compressionParameters ZSTD_getCParams_internal(int compressionLevel, else if (compressionLevel > ZSTD_MAX_CLEVEL) row = ZSTD_MAX_CLEVEL; else row = compressionLevel; +#ifdef ZSTD_LAZY_SKIP_LONG_MATCHES + { ZSTD_compressionParameters cp = + ((unsigned)(compressionLevel - 6) <= 1) /* compressionLevel == 6 || compressionLevel == 7 */ + ? ZSTD_CParametersFastCompress[tableID][compressionLevel - 6] + : ZSTD_defaultCParameters[tableID][row]; +#else { ZSTD_compressionParameters cp = ZSTD_defaultCParameters[tableID][row]; +#endif DEBUGLOG(5, "ZSTD_getCParams_internal selected tableID: %u row: %u strat: %u", tableID, row, (U32)cp.strategy); /* acceleration factor */ if (compressionLevel < 0) { diff --git a/lib/compress/zstd_compress_internal.h b/lib/compress/zstd_compress_internal.h index 13a394b38..6bf10f35d 100644 --- a/lib/compress/zstd_compress_internal.h +++ b/lib/compress/zstd_compress_internal.h @@ -39,6 +39,38 @@ The benefit is that ZSTD_DUBT_UNSORTED_MARK cannot be mishandled after table reuse with a different strategy. This constant is required by ZSTD_compressBlock_btlazy2() and ZSTD_reduceTable_internal() */ +#ifdef ZSTD_LAZY_SKIP_LONG_MATCHES +/* Lazy evaluation is performed only if the first match length + * is <= ZSTD_compressFastLazyLimit */ +#define ZSTD_COMPRESS_FAST_BASE_LAZY_LIMIT 5 + +#define ZSTD_MAX_CLEVEL 22 +static const size_t ZSTD_compressFastLazyLimit[ZSTD_MAX_CLEVEL + 1] = { + ZSTD_COMPRESS_FAST_BASE_LAZY_LIMIT, /* base for negative levels */ + 0, /* level 1 */ + 0, /* level 2 */ + 0, /* level 3 */ + 0, /* level 4 */ + 0, /* level 5 */ + ZSTD_COMPRESS_FAST_BASE_LAZY_LIMIT, /* level 6 */ + ZSTD_COMPRESS_FAST_BASE_LAZY_LIMIT + 1, /* level 7 */ + 0, /* level 8.*/ + 0, /* level 9.*/ + 0, /* level 10.*/ + 0, /* level 11.*/ + 0, /* level 12.*/ + 0, /* level 13 */ + 0, /* level 14 */ + 0, /* level 15 */ + 0, /* level 16 */ + 0, /* level 17 */ + 0, /* level 18 */ + 0, /* level 19 */ + 0, /* level 20 */ + 0, /* level 21 */ + 0, /* level 22 */ +}; +#endif /*-************************************* * Context memory management @@ -105,6 +137,9 @@ typedef struct { BYTE* ofCode; size_t maxNbSeq; size_t maxNbLit; +#ifdef ZSTD_LAZY_SKIP_LONG_MATCHES + size_t lazyLimit; /* Match length limit to allow lazy evaluation */ +#endif /* longLengthPos and longLengthType to allow us to represent either a single litLength or matchLength * in the seqStore that has a value larger than U16 (if it exists). To do so, we increment diff --git a/lib/compress/zstd_lazy.c b/lib/compress/zstd_lazy.c index 18b7b4394..9f6c68ce8 100644 --- a/lib/compress/zstd_lazy.c +++ b/lib/compress/zstd_lazy.c @@ -1590,6 +1590,9 @@ size_t ZSTD_compressBlock_lazy_generic( prefixLowestIndex - (U32)(dictEnd - dictBase) : 0; const U32 dictAndPrefixLength = (U32)((ip - prefixLowest) + (dictEnd - dictLowest)); +#ifdef ZSTD_LAZY_SKIP_LONG_MATCHES + size_t lazyLimit = seqStore->lazyLimit; +#endif DEBUGLOG(5, "ZSTD_compressBlock_lazy_generic (dictMode=%u) (searchFunc=%u)", (U32)dictMode, (U32)searchMethod); ip += (dictAndPrefixLength == 0); @@ -1669,7 +1672,11 @@ size_t ZSTD_compressBlock_lazy_generic( } /* let's try to find a better solution */ +#ifdef ZSTD_LAZY_SKIP_LONG_MATCHES + if ((lazyLimit == 0 || matchLength <= lazyLimit) && depth >= 1) /* restrict lazy eval to short matches only */ +#else if (depth>=1) +#endif while (ip