mirror of
https://github.com/facebook/zstd.git
synced 2025-07-04 06:40:29 +02:00
improve compression ratio of small alphabets
fix #3328 In situations where the alphabet size is very small, the evaluation of literal costs from the Optimal Parser is initially incorrect. It takes some time to converge, during which compression is less efficient. This is especially important for small files, because there will not be enough data to converge, so most of the parsing is selected based on incorrect metrics. After this patch, the scenario ##3328 gets fixed, delivering the expected 29 bytes compressed size (smallest known compressed size).
This commit is contained in:
@ -386,7 +386,7 @@ static U32 HUF_setMaxHeight(nodeElt* huffNode, U32 lastNonNull, U32 targetNbBits
|
|||||||
|
|
||||||
/* renorm totalCost from 2^largestBits to 2^targetNbBits
|
/* renorm totalCost from 2^largestBits to 2^targetNbBits
|
||||||
* note : totalCost is necessarily a multiple of baseCost */
|
* note : totalCost is necessarily a multiple of baseCost */
|
||||||
assert((totalCost & (baseCost - 1)) == 0);
|
assert(((U32)totalCost & (baseCost - 1)) == 0);
|
||||||
totalCost >>= (largestBits - targetNbBits);
|
totalCost >>= (largestBits - targetNbBits);
|
||||||
assert(totalCost > 0);
|
assert(totalCost > 0);
|
||||||
|
|
||||||
@ -1253,7 +1253,14 @@ unsigned HUF_minTableLog(unsigned symbolCardinality)
|
|||||||
return minBitsSymbols;
|
return minBitsSymbols;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned HUF_optimalTableLog(unsigned maxTableLog, size_t srcSize, unsigned maxSymbolValue, void* workSpace, size_t wkspSize, HUF_CElt* table, const unsigned* count, HUF_depth_mode depthMode)
|
unsigned HUF_optimalTableLog(
|
||||||
|
unsigned maxTableLog,
|
||||||
|
size_t srcSize,
|
||||||
|
unsigned maxSymbolValue,
|
||||||
|
void* workSpace, size_t wkspSize,
|
||||||
|
HUF_CElt* table,
|
||||||
|
const unsigned* count,
|
||||||
|
HUF_depth_mode depthMode)
|
||||||
{
|
{
|
||||||
unsigned optLog = FSE_optimalTableLog_internal(maxTableLog, srcSize, maxSymbolValue, 1);
|
unsigned optLog = FSE_optimalTableLog_internal(maxTableLog, srcSize, maxSymbolValue, 1);
|
||||||
assert(srcSize > 1); /* Not supported, RLE should be used instead */
|
assert(srcSize > 1); /* Not supported, RLE should be used instead */
|
||||||
@ -1267,12 +1274,13 @@ unsigned HUF_optimalTableLog(unsigned maxTableLog, size_t srcSize, unsigned maxS
|
|||||||
size_t optSize = ((size_t) ~0) - 1;
|
size_t optSize = ((size_t) ~0) - 1;
|
||||||
unsigned optLogGuess;
|
unsigned optLogGuess;
|
||||||
|
|
||||||
if (wkspSize < sizeof(HUF_buildCTable_wksp_tables)) return optLog; /** Assert workspace is large enough **/
|
DEBUGLOG(6, "HUF_optimalTableLog: probing huf depth (srcSize=%zu)", srcSize);
|
||||||
|
if (wkspSize < sizeof(HUF_buildCTable_wksp_tables)) return optLog; /* silently return if workspace is not large enough */
|
||||||
|
|
||||||
/* Search until size increases */
|
/* Search until size increases */
|
||||||
for (optLogGuess = minTableLog; optLogGuess <= maxTableLog; optLogGuess++) {
|
for (optLogGuess = minTableLog; optLogGuess <= maxTableLog; optLogGuess++) {
|
||||||
|
DEBUGLOG(7, "checking for huffLog=%u", optLogGuess);
|
||||||
maxBits = HUF_buildCTable_wksp(table, count, maxSymbolValue, optLogGuess, workSpace, wkspSize);
|
maxBits = HUF_buildCTable_wksp(table, count, maxSymbolValue, optLogGuess, workSpace, wkspSize);
|
||||||
|
|
||||||
if (ERR_isError(maxBits)) continue;
|
if (ERR_isError(maxBits)) continue;
|
||||||
|
|
||||||
if (maxBits < optLogGuess && optLogGuess > minTableLog) break;
|
if (maxBits < optLogGuess && optLogGuess > minTableLog) break;
|
||||||
|
@ -2727,15 +2727,14 @@ ZSTD_entropyCompressSeqStore_internal(
|
|||||||
unsigned const suspectUncompressible = (numSequences == 0) || (numLiterals / numSequences >= SUSPECT_UNCOMPRESSIBLE_LITERAL_RATIO);
|
unsigned const suspectUncompressible = (numSequences == 0) || (numLiterals / numSequences >= SUSPECT_UNCOMPRESSIBLE_LITERAL_RATIO);
|
||||||
size_t const litSize = (size_t)(seqStorePtr->lit - literals);
|
size_t const litSize = (size_t)(seqStorePtr->lit - literals);
|
||||||
|
|
||||||
HUF_depth_mode depthMode = cctxParams->cParams.strategy >= HUF_OPTIMAL_DEPTH_THRESHOLD ? HUF_depth_optimal : HUF_depth_fast;
|
|
||||||
size_t const cSize = ZSTD_compressLiterals(
|
size_t const cSize = ZSTD_compressLiterals(
|
||||||
&prevEntropy->huf, &nextEntropy->huf,
|
|
||||||
cctxParams->cParams.strategy,
|
|
||||||
ZSTD_literalsCompressionIsDisabled(cctxParams),
|
|
||||||
op, dstCapacity,
|
op, dstCapacity,
|
||||||
literals, litSize,
|
literals, litSize,
|
||||||
entropyWorkspace, entropyWkspSize,
|
entropyWorkspace, entropyWkspSize,
|
||||||
bmi2, suspectUncompressible, depthMode);
|
&prevEntropy->huf, &nextEntropy->huf,
|
||||||
|
cctxParams->cParams.strategy,
|
||||||
|
ZSTD_literalsCompressionIsDisabled(cctxParams),
|
||||||
|
suspectUncompressible, bmi2);
|
||||||
FORWARD_IF_ERROR(cSize, "ZSTD_compressLiterals failed");
|
FORWARD_IF_ERROR(cSize, "ZSTD_compressLiterals failed");
|
||||||
assert(cSize <= dstCapacity);
|
assert(cSize <= dstCapacity);
|
||||||
op += cSize;
|
op += cSize;
|
||||||
@ -3878,12 +3877,12 @@ ZSTD_deriveBlockSplitsHelper(seqStoreSplits* splits, size_t startIdx, size_t end
|
|||||||
size_t estimatedSecondHalfSize;
|
size_t estimatedSecondHalfSize;
|
||||||
size_t midIdx = (startIdx + endIdx)/2;
|
size_t midIdx = (startIdx + endIdx)/2;
|
||||||
|
|
||||||
|
DEBUGLOG(5, "ZSTD_deriveBlockSplitsHelper: startIdx=%zu endIdx=%zu", startIdx, endIdx);
|
||||||
assert(endIdx >= startIdx);
|
assert(endIdx >= startIdx);
|
||||||
if (endIdx - startIdx < MIN_SEQUENCES_BLOCK_SPLITTING || splits->idx >= ZSTD_MAX_NB_BLOCK_SPLITS) {
|
if (endIdx - startIdx < MIN_SEQUENCES_BLOCK_SPLITTING || splits->idx >= ZSTD_MAX_NB_BLOCK_SPLITS) {
|
||||||
DEBUGLOG(6, "ZSTD_deriveBlockSplitsHelper: Too few sequences (%zu)", endIdx - startIdx);
|
DEBUGLOG(6, "ZSTD_deriveBlockSplitsHelper: Too few sequences (%zu)", endIdx - startIdx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DEBUGLOG(5, "ZSTD_deriveBlockSplitsHelper: startIdx=%zu endIdx=%zu", startIdx, endIdx);
|
|
||||||
ZSTD_deriveSeqStoreChunk(fullSeqStoreChunk, origSeqStore, startIdx, endIdx);
|
ZSTD_deriveSeqStoreChunk(fullSeqStoreChunk, origSeqStore, startIdx, endIdx);
|
||||||
ZSTD_deriveSeqStoreChunk(firstHalfSeqStore, origSeqStore, startIdx, midIdx);
|
ZSTD_deriveSeqStoreChunk(firstHalfSeqStore, origSeqStore, startIdx, midIdx);
|
||||||
ZSTD_deriveSeqStoreChunk(secondHalfSeqStore, origSeqStore, midIdx, endIdx);
|
ZSTD_deriveSeqStoreChunk(secondHalfSeqStore, origSeqStore, midIdx, endIdx);
|
||||||
|
@ -92,16 +92,37 @@ size_t ZSTD_compressRleLiteralsBlock (void* dst, size_t dstCapacity, const void*
|
|||||||
return flSize+1;
|
return flSize+1;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t ZSTD_compressLiterals (ZSTD_hufCTables_t const* prevHuf,
|
/* ZSTD_minLiteralsToCompress() :
|
||||||
ZSTD_hufCTables_t* nextHuf,
|
* returns minimal amount of literals
|
||||||
ZSTD_strategy strategy, int disableLiteralCompression,
|
* for literal compression to even be attempted.
|
||||||
void* dst, size_t dstCapacity,
|
* Minimum is made tighter as compression strategy increases.
|
||||||
const void* src, size_t srcSize,
|
*/
|
||||||
void* entropyWorkspace, size_t entropyWorkspaceSize,
|
static size_t
|
||||||
const int bmi2,
|
ZSTD_minLiteralsToCompress(ZSTD_strategy strategy, HUF_repeat huf_repeat)
|
||||||
unsigned suspectUncompressible, HUF_depth_mode depthMode)
|
{
|
||||||
|
assert((int)strategy >= 0);
|
||||||
|
assert((int)strategy <= 9);
|
||||||
|
/* btultra2 : min 8 bytes;
|
||||||
|
* then 2x larger for each successive compression strategy
|
||||||
|
* max threshold 64 bytes */
|
||||||
|
{ int const shift = MIN(9-strategy, 3);
|
||||||
|
size_t const mintc = (huf_repeat == HUF_repeat_valid) ? 6 : 8 << shift;
|
||||||
|
DEBUGLOG(7, "minLiteralsToCompress = %zu", mintc);
|
||||||
|
return mintc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ZSTD_compressLiterals (
|
||||||
|
void* dst, size_t dstCapacity,
|
||||||
|
const void* src, size_t srcSize,
|
||||||
|
void* entropyWorkspace, size_t entropyWorkspaceSize,
|
||||||
|
const ZSTD_hufCTables_t* prevHuf,
|
||||||
|
ZSTD_hufCTables_t* nextHuf,
|
||||||
|
ZSTD_strategy strategy,
|
||||||
|
int disableLiteralCompression,
|
||||||
|
int suspectUncompressible,
|
||||||
|
int bmi2)
|
||||||
{
|
{
|
||||||
size_t const minGain = ZSTD_minGain(srcSize, strategy);
|
|
||||||
size_t const lhSize = 3 + (srcSize >= 1 KB) + (srcSize >= 16 KB);
|
size_t const lhSize = 3 + (srcSize >= 1 KB) + (srcSize >= 16 KB);
|
||||||
BYTE* const ostart = (BYTE*)dst;
|
BYTE* const ostart = (BYTE*)dst;
|
||||||
U32 singleStream = srcSize < 256;
|
U32 singleStream = srcSize < 256;
|
||||||
@ -119,15 +140,14 @@ size_t ZSTD_compressLiterals (ZSTD_hufCTables_t const* prevHuf,
|
|||||||
if (disableLiteralCompression)
|
if (disableLiteralCompression)
|
||||||
return ZSTD_noCompressLiterals(dst, dstCapacity, src, srcSize);
|
return ZSTD_noCompressLiterals(dst, dstCapacity, src, srcSize);
|
||||||
|
|
||||||
/* small ? don't even attempt compression (speed opt) */
|
/* if too small, don't even attempt compression (speed opt) */
|
||||||
# define COMPRESS_LITERALS_SIZE_MIN 63
|
if (srcSize < ZSTD_minLiteralsToCompress(strategy, prevHuf->repeatMode))
|
||||||
{ size_t const minLitSize = (prevHuf->repeatMode == HUF_repeat_valid) ? 6 : COMPRESS_LITERALS_SIZE_MIN;
|
return ZSTD_noCompressLiterals(dst, dstCapacity, src, srcSize);
|
||||||
if (srcSize <= minLitSize) return ZSTD_noCompressLiterals(dst, dstCapacity, src, srcSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
RETURN_ERROR_IF(dstCapacity < lhSize+1, dstSize_tooSmall, "not enough space for compression");
|
RETURN_ERROR_IF(dstCapacity < lhSize+1, dstSize_tooSmall, "not enough space for compression");
|
||||||
{ HUF_repeat repeat = prevHuf->repeatMode;
|
{ HUF_repeat repeat = prevHuf->repeatMode;
|
||||||
int const preferRepeat = (strategy < ZSTD_lazy) ? srcSize <= 1024 : 0;
|
int const preferRepeat = (strategy < ZSTD_lazy) ? srcSize <= 1024 : 0;
|
||||||
|
HUF_depth_mode const depthMode = (strategy >= HUF_OPTIMAL_DEPTH_THRESHOLD) ? HUF_depth_optimal : HUF_depth_fast;
|
||||||
typedef size_t (*huf_compress_f)(void*, size_t, const void*, size_t, unsigned, unsigned, void*, size_t, HUF_CElt*, HUF_repeat*, int, int, unsigned, HUF_depth_mode);
|
typedef size_t (*huf_compress_f)(void*, size_t, const void*, size_t, unsigned, unsigned, void*, size_t, HUF_CElt*, HUF_repeat*, int, int, unsigned, HUF_depth_mode);
|
||||||
huf_compress_f huf_compress;
|
huf_compress_f huf_compress;
|
||||||
if (repeat == HUF_repeat_valid && lhSize == 3) singleStream = 1;
|
if (repeat == HUF_repeat_valid && lhSize == 3) singleStream = 1;
|
||||||
@ -146,10 +166,11 @@ size_t ZSTD_compressLiterals (ZSTD_hufCTables_t const* prevHuf,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((cLitSize==0) || (cLitSize >= srcSize - minGain) || ERR_isError(cLitSize)) {
|
{ size_t const minGain = ZSTD_minGain(srcSize, strategy);
|
||||||
ZSTD_memcpy(nextHuf, prevHuf, sizeof(*prevHuf));
|
if ((cLitSize==0) || (cLitSize >= srcSize - minGain) || ERR_isError(cLitSize)) {
|
||||||
return ZSTD_noCompressLiterals(dst, dstCapacity, src, srcSize);
|
ZSTD_memcpy(nextHuf, prevHuf, sizeof(*prevHuf));
|
||||||
}
|
return ZSTD_noCompressLiterals(dst, dstCapacity, src, srcSize);
|
||||||
|
} }
|
||||||
if (cLitSize==1) {
|
if (cLitSize==1) {
|
||||||
ZSTD_memcpy(nextHuf, prevHuf, sizeof(*prevHuf));
|
ZSTD_memcpy(nextHuf, prevHuf, sizeof(*prevHuf));
|
||||||
return ZSTD_compressRleLiteralsBlock(dst, dstCapacity, src, srcSize);
|
return ZSTD_compressRleLiteralsBlock(dst, dstCapacity, src, srcSize);
|
||||||
|
@ -18,14 +18,18 @@ size_t ZSTD_noCompressLiterals (void* dst, size_t dstCapacity, const void* src,
|
|||||||
|
|
||||||
size_t ZSTD_compressRleLiteralsBlock (void* dst, size_t dstCapacity, const void* src, size_t srcSize);
|
size_t ZSTD_compressRleLiteralsBlock (void* dst, size_t dstCapacity, const void* src, size_t srcSize);
|
||||||
|
|
||||||
/* If suspectUncompressible then some sampling checks will be run to potentially skip huffman coding */
|
/* ZSTD_compressLiterals():
|
||||||
size_t ZSTD_compressLiterals (ZSTD_hufCTables_t const* prevHuf,
|
* @entropyWorkspace: must be aligned on 4-bytes boundaries
|
||||||
ZSTD_hufCTables_t* nextHuf,
|
* @entropyWorkspaceSize : must be >= HUF_WORKSPACE_SIZE
|
||||||
ZSTD_strategy strategy, int disableLiteralCompression,
|
* @suspectUncompressible: sampling checks, to potentially skip huffman coding
|
||||||
void* dst, size_t dstCapacity,
|
*/
|
||||||
|
size_t ZSTD_compressLiterals (void* dst, size_t dstCapacity,
|
||||||
const void* src, size_t srcSize,
|
const void* src, size_t srcSize,
|
||||||
void* entropyWorkspace, size_t entropyWorkspaceSize,
|
void* entropyWorkspace, size_t entropyWorkspaceSize,
|
||||||
const int bmi2,
|
const ZSTD_hufCTables_t* prevHuf,
|
||||||
unsigned suspectUncompressible, HUF_depth_mode depthMode);
|
ZSTD_hufCTables_t* nextHuf,
|
||||||
|
ZSTD_strategy strategy, int disableLiteralCompression,
|
||||||
|
int suspectUncompressible,
|
||||||
|
int bmi2);
|
||||||
|
|
||||||
#endif /* ZSTD_COMPRESS_LITERALS_H */
|
#endif /* ZSTD_COMPRESS_LITERALS_H */
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
#define ZSTD_LITFREQ_ADD 2 /* scaling factor for litFreq, so that frequencies adapt faster to new stats */
|
#define ZSTD_LITFREQ_ADD 2 /* scaling factor for litFreq, so that frequencies adapt faster to new stats */
|
||||||
#define ZSTD_MAX_PRICE (1<<30)
|
#define ZSTD_MAX_PRICE (1<<30)
|
||||||
|
|
||||||
#define ZSTD_PREDEF_THRESHOLD 1024 /* if srcSize < ZSTD_PREDEF_THRESHOLD, symbols' cost is assumed static, directly determined by pre-defined distributions */
|
#define ZSTD_PREDEF_THRESHOLD 8 /* if srcSize < ZSTD_PREDEF_THRESHOLD, symbols' cost is assumed static, directly determined by pre-defined distributions */
|
||||||
|
|
||||||
|
|
||||||
/*-*************************************
|
/*-*************************************
|
||||||
@ -96,14 +96,18 @@ static U32 sum_u32(const unsigned table[], size_t nbElts)
|
|||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
static U32 ZSTD_downscaleStats(unsigned* table, U32 lastEltIndex, U32 shift)
|
typedef enum { base_0possible=0, base_1guaranteed=1 } base_directive_e;
|
||||||
|
|
||||||
|
static U32
|
||||||
|
ZSTD_downscaleStats(unsigned* table, U32 lastEltIndex, U32 shift, base_directive_e base1)
|
||||||
{
|
{
|
||||||
U32 s, sum=0;
|
U32 s, sum=0;
|
||||||
DEBUGLOG(5, "ZSTD_downscaleStats (nbElts=%u, shift=%u)",
|
DEBUGLOG(5, "ZSTD_downscaleStats (nbElts=%u, shift=%u)",
|
||||||
(unsigned)lastEltIndex+1, (unsigned)shift );
|
(unsigned)lastEltIndex+1, (unsigned)shift );
|
||||||
assert(shift < 30);
|
assert(shift < 30);
|
||||||
for (s=0; s<lastEltIndex+1; s++) {
|
for (s=0; s<lastEltIndex+1; s++) {
|
||||||
unsigned newStat = 1 + (table[s] >> shift);
|
unsigned const base = base1 ? 1 : (table[s]>0);
|
||||||
|
unsigned const newStat = base + (table[s] >> shift);
|
||||||
sum += newStat;
|
sum += newStat;
|
||||||
table[s] = newStat;
|
table[s] = newStat;
|
||||||
}
|
}
|
||||||
@ -120,7 +124,7 @@ static U32 ZSTD_scaleStats(unsigned* table, U32 lastEltIndex, U32 logTarget)
|
|||||||
DEBUGLOG(5, "ZSTD_scaleStats (nbElts=%u, target=%u)", (unsigned)lastEltIndex+1, (unsigned)logTarget);
|
DEBUGLOG(5, "ZSTD_scaleStats (nbElts=%u, target=%u)", (unsigned)lastEltIndex+1, (unsigned)logTarget);
|
||||||
assert(logTarget < 30);
|
assert(logTarget < 30);
|
||||||
if (factor <= 1) return prevsum;
|
if (factor <= 1) return prevsum;
|
||||||
return ZSTD_downscaleStats(table, lastEltIndex, ZSTD_highbit32(factor));
|
return ZSTD_downscaleStats(table, lastEltIndex, ZSTD_highbit32(factor), base_1guaranteed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ZSTD_rescaleFreqs() :
|
/* ZSTD_rescaleFreqs() :
|
||||||
@ -202,14 +206,14 @@ ZSTD_rescaleFreqs(optState_t* const optPtr,
|
|||||||
optPtr->offCodeSum += optPtr->offCodeFreq[of];
|
optPtr->offCodeSum += optPtr->offCodeFreq[of];
|
||||||
} }
|
} }
|
||||||
|
|
||||||
} else { /* huf.repeatMode != HUF_repeat_valid => presumed not a dictionary */
|
} else { /* first block, no dictionary */
|
||||||
|
|
||||||
assert(optPtr->litFreq != NULL);
|
assert(optPtr->litFreq != NULL);
|
||||||
if (compressedLiterals) {
|
if (compressedLiterals) {
|
||||||
/* base initial cost of literals on direct frequency within src */
|
/* base initial cost of literals on direct frequency within src */
|
||||||
unsigned lit = MaxLit;
|
unsigned lit = MaxLit;
|
||||||
HIST_count_simple(optPtr->litFreq, &lit, src, srcSize); /* use raw first block to init statistics */
|
HIST_count_simple(optPtr->litFreq, &lit, src, srcSize); /* use raw first block to init statistics */
|
||||||
optPtr->litSum = ZSTD_downscaleStats(optPtr->litFreq, MaxLit, 8);
|
optPtr->litSum = ZSTD_downscaleStats(optPtr->litFreq, MaxLit, 8, base_0possible);
|
||||||
}
|
}
|
||||||
|
|
||||||
{ unsigned const baseLLfreqs[MaxLL+1] = {
|
{ unsigned const baseLLfreqs[MaxLL+1] = {
|
||||||
|
Reference in New Issue
Block a user