diff --git a/src/shrink_block_v2.c b/src/shrink_block_v2.c index 96452c4..4d2ff7d 100644 --- a/src/shrink_block_v2.c +++ b/src/shrink_block_v2.c @@ -183,12 +183,12 @@ static inline int lzsa_write_match_varlen_v2(unsigned char *pOutData, int nOutOf * @param nStartOffset current offset in input window (typically the number of previously compressed bytes) * @param nEndOffset offset to end finding matches at (typically the size of the total input window in bytes */ -static void lzsa_optimize_forward_v2(lzsa_compressor *pCompressor, const unsigned char *pInWindow, const int nStartOffset, const int nEndOffset) { +static void lzsa_optimize_forward_v2(lzsa_compressor *pCompressor, const unsigned char *pInWindow, lzsa_match *pBestMatch, const int nStartOffset, const int nEndOffset, const int nReduce) { lzsa_arrival *arrival = pCompressor->arrival; const int nFavorRatio = (pCompressor->flags & LZSA_FLAG_FAVOR_RATIO) ? 1 : 0; const int nMinMatchSize = pCompressor->min_match_size; + const int nDisableScore = nReduce ? 0 : (2 * BLOCK_SIZE); int i, j, n; - lzsa_match match[32]; memset(arrival + (nStartOffset << MATCHES_PER_OFFSET_SHIFT), 0, sizeof(lzsa_arrival) * ((nEndOffset - nStartOffset) << MATCHES_PER_OFFSET_SHIFT)); @@ -199,7 +199,7 @@ static void lzsa_optimize_forward_v2(lzsa_compressor *pCompressor, const unsigne arrival[nStartOffset << MATCHES_PER_OFFSET_SHIFT].from_slot = -1; for (i = nStartOffset; i != (nEndOffset - 1); i++) { - int m, nMatches; + int m; for (j = 0; j < NMATCHES_PER_OFFSET && arrival[(i << MATCHES_PER_OFFSET_SHIFT) + j].from_slot; j++) { const int nPrevCost = arrival[(i << MATCHES_PER_OFFSET_SHIFT) + j].cost & 0x3fffffff; @@ -232,9 +232,11 @@ static void lzsa_optimize_forward_v2(lzsa_compressor *pCompressor, const unsigne } if (!exists) { + int nScore = arrival[(i << MATCHES_PER_OFFSET_SHIFT) + j].score + 1; for (n = 0; n < NMATCHES_PER_OFFSET; n++) { lzsa_arrival *pDestArrival = &pDestSlots[n]; - if (nCodingChoiceCost <= pDestArrival->cost) { + if (nCodingChoiceCost < pDestArrival->cost || + (nCodingChoiceCost == pDestArrival->cost && nScore < (pDestArrival->score + nDisableScore))) { if (pDestArrival->from_slot) { memmove(&pDestSlots[n + 1], @@ -248,6 +250,7 @@ static void lzsa_optimize_forward_v2(lzsa_compressor *pCompressor, const unsigne pDestArrival->match_offset = 0; pDestArrival->match_len = 0; pDestArrival->num_literals = nNumLiterals; + pDestArrival->score = nScore; pDestArrival->rep_offset = arrival[(i << MATCHES_PER_OFFSET_SHIFT) + j].rep_offset; break; } @@ -256,9 +259,9 @@ static void lzsa_optimize_forward_v2(lzsa_compressor *pCompressor, const unsigne } } - nMatches = lzsa_find_matches_at(pCompressor, i, match, 32); + lzsa_match *match = pCompressor->match + (i << 5); - for (m = 0; m < nMatches; m++) { + for (m = 0; m < 32 && match[m].length; m++) { int nMatchLen = match[m].length; int nMatchOffset = match[m].offset; int nNoRepmatchOffsetCost = (nMatchOffset <= 32) ? 4 : ((nMatchOffset <= 512) ? 8 : ((nMatchOffset <= (8192 + 512)) ? 12 : 16)); @@ -319,10 +322,13 @@ static void lzsa_optimize_forward_v2(lzsa_compressor *pCompressor, const unsigne } if (!exists) { + int nScore = arrival[(i << MATCHES_PER_OFFSET_SHIFT) + j].score + ((nMatchOffset == nRepOffset) ? 2 : 3); + for (n = 0; n < NMATCHES_PER_OFFSET; n++) { lzsa_arrival *pDestArrival = &pDestSlots[n]; - if (nCodingChoiceCost <= pDestArrival->cost) { + if (nCodingChoiceCost < pDestArrival->cost || + (nCodingChoiceCost == pDestArrival->cost && nScore < (pDestArrival->score + nDisableScore))) { if (pDestArrival->from_slot) { memmove(&pDestSlots[n + 1], &pDestSlots[n], @@ -335,6 +341,7 @@ static void lzsa_optimize_forward_v2(lzsa_compressor *pCompressor, const unsigne pDestArrival->match_offset = nMatchOffset; pDestArrival->match_len = k; pDestArrival->num_literals = 0; + pDestArrival->score = nScore; pDestArrival->rep_offset = nMatchOffset; break; } @@ -361,10 +368,13 @@ static void lzsa_optimize_forward_v2(lzsa_compressor *pCompressor, const unsigne } if (!exists) { + int nScore = arrival[(i << MATCHES_PER_OFFSET_SHIFT) + j].score + 2; + for (n = 0; n < NMATCHES_PER_OFFSET; n++) { lzsa_arrival *pDestArrival = &pDestSlots[n]; - if (nRepCodingChoiceCost <= pDestArrival->cost) { + if (nRepCodingChoiceCost < pDestArrival->cost || + (nRepCodingChoiceCost == pDestArrival->cost && nScore < (pDestArrival->score + nDisableScore))) { if (pDestArrival->from_slot) { memmove(&pDestSlots[n + 1], &pDestSlots[n], @@ -377,6 +387,7 @@ static void lzsa_optimize_forward_v2(lzsa_compressor *pCompressor, const unsigne pDestArrival->match_offset = nRepOffset; pDestArrival->match_len = k; pDestArrival->num_literals = 0; + pDestArrival->score = nScore; pDestArrival->rep_offset = nRepOffset; break; } @@ -390,12 +401,12 @@ static void lzsa_optimize_forward_v2(lzsa_compressor *pCompressor, const unsigne } lzsa_arrival *end_arrival = &arrival[(i << MATCHES_PER_OFFSET_SHIFT) + 0]; - pCompressor->best_match[i].length = 0; - pCompressor->best_match[i].offset = 0; + pBestMatch[i].length = 0; + pBestMatch[i].offset = 0; while (end_arrival->from_slot > 0 && end_arrival->from_pos >= 0) { - pCompressor->best_match[end_arrival->from_pos].length = end_arrival->match_len; - pCompressor->best_match[end_arrival->from_pos].offset = end_arrival->match_offset; + pBestMatch[end_arrival->from_pos].length = end_arrival->match_len; + pBestMatch[end_arrival->from_pos].offset = end_arrival->match_offset; end_arrival = &arrival[(end_arrival->from_pos << MATCHES_PER_OFFSET_SHIFT) + (end_arrival->from_slot - 1)]; } } @@ -568,6 +579,78 @@ static int lzsa_optimize_command_count_v2(lzsa_compressor *pCompressor, const un return nDidReduce; } +/** + * Get compressed data block size + * + * @param pCompressor compression context + * @param pBestMatch optimal matches to emit + * @param nStartOffset current offset in input window (typically the number of previously compressed bytes) + * @param nEndOffset offset to end finding matches at (typically the size of the total input window in bytes + * + * @return size of compressed data that will be written to output buffer + */ +static int lzsa_get_compressed_size_v2(lzsa_compressor *pCompressor, lzsa_match *pBestMatch, const int nStartOffset, const int nEndOffset) { + int i; + int nNumLiterals = 0; + int nOutOffset = 0; + int nRepMatchOffset = 0; + int nCompressedSize = 0; + + for (i = nStartOffset; i < nEndOffset; ) { + const lzsa_match *pMatch = pBestMatch + i; + + if (pMatch->length >= MIN_MATCH_SIZE_V2) { + int nMatchOffset = pMatch->offset; + int nMatchLen = pMatch->length; + int nEncodedMatchLen = nMatchLen - MIN_MATCH_SIZE_V2; + int nOffsetSize; + + if (nMatchOffset == nRepMatchOffset) { + nOffsetSize = 0; + } + else { + if (nMatchOffset <= 32) { + nOffsetSize = 4; + } + else if (nMatchOffset <= 512) { + nOffsetSize = 8; + } + else if (nMatchOffset <= (8192 + 512)) { + nOffsetSize = 12; + } + else { + nOffsetSize = 16; + } + } + + int nCommandSize = 8 /* token */ + lzsa_get_literals_varlen_size_v2(nNumLiterals) + (nNumLiterals << 3) + nOffsetSize /* match offset */ + lzsa_get_match_varlen_size_v2(nEncodedMatchLen); + nCompressedSize += nCommandSize; + + nNumLiterals = 0; + nRepMatchOffset = nMatchOffset; + i += nMatchLen; + } + else { + nNumLiterals++; + i++; + } + } + + { + int nTokenLiteralsLen = (nNumLiterals >= LITERALS_RUN_LEN_V2) ? LITERALS_RUN_LEN_V2 : nNumLiterals; + int nCommandSize = 8 /* token */ + lzsa_get_literals_varlen_size_v2(nNumLiterals) + (nNumLiterals << 3); + + nCompressedSize += nCommandSize; + nNumLiterals = 0; + } + + if (pCompressor->flags & LZSA_FLAG_RAW_BLOCK) { + nCompressedSize += (8 + 4 + 8); + } + + return nCompressedSize; +} + /** * Emit block of compressed data * @@ -852,9 +935,21 @@ static int lzsa_write_raw_uncompressed_block_v2(lzsa_compressor *pCompressor, co * @return size of compressed data in output buffer, or -1 if the data is uncompressible */ int lzsa_optimize_and_write_block_v2(lzsa_compressor *pCompressor, const unsigned char *pInWindow, const int nPreviousBlockSize, const int nInDataSize, unsigned char *pOutData, const int nMaxOutDataSize) { - int nResult; + int i, nResult, nBaseCompressedSize; - lzsa_optimize_forward_v2(pCompressor, pInWindow, nPreviousBlockSize, nPreviousBlockSize + nInDataSize); + /* Find matches */ + for (i = nPreviousBlockSize; i != (nPreviousBlockSize + nInDataSize - 1); i++) { + int nMatches = lzsa_find_matches_at(pCompressor, i, pCompressor->match + (i << 5), 32); + while (nMatches < 32) { + pCompressor->match[(i << 5) + nMatches].offset = 0; + pCompressor->match[(i << 5) + nMatches].length = 0; + nMatches++; + } + } + + /* Compress optimally without breaking ties in favor of less tokens */ + + lzsa_optimize_forward_v2(pCompressor, pInWindow, pCompressor->best_match, nPreviousBlockSize, nPreviousBlockSize + nInDataSize, 0 /* reduce */); int nDidReduce; int nPasses = 0; @@ -863,7 +958,29 @@ int lzsa_optimize_and_write_block_v2(lzsa_compressor *pCompressor, const unsigne nPasses++; } while (nDidReduce && nPasses < 20); - nResult = lzsa_write_block_v2(pCompressor, pCompressor->best_match, pInWindow, nPreviousBlockSize, nPreviousBlockSize + nInDataSize, pOutData, nMaxOutDataSize); + nBaseCompressedSize = lzsa_get_compressed_size_v2(pCompressor, pCompressor->best_match, nPreviousBlockSize, nPreviousBlockSize + nInDataSize); + lzsa_match *pBestMatch = pCompressor->best_match; + + if (nBaseCompressedSize > 0 && nInDataSize < 65536) { + int nReducedCompressedSize; + + /* Compress optimally and do break ties in favor of less tokens */ + lzsa_optimize_forward_v2(pCompressor, pInWindow, pCompressor->improved_match, nPreviousBlockSize, nPreviousBlockSize + nInDataSize, 1 /* reduce */); + + nPasses = 0; + do { + nDidReduce = lzsa_optimize_command_count_v2(pCompressor, pInWindow, pCompressor->improved_match, nPreviousBlockSize, nPreviousBlockSize + nInDataSize); + nPasses++; + } while (nDidReduce && nPasses < 20); + + nReducedCompressedSize = lzsa_get_compressed_size_v2(pCompressor, pCompressor->improved_match, nPreviousBlockSize, nPreviousBlockSize + nInDataSize); + if (nReducedCompressedSize > 0 && nReducedCompressedSize <= nBaseCompressedSize) { + /* Pick the parse with the reduced number of tokens as it didn't negatively affect the size */ + pBestMatch = pCompressor->improved_match; + } + } + + nResult = lzsa_write_block_v2(pCompressor, pBestMatch, pInWindow, nPreviousBlockSize, nPreviousBlockSize + nInDataSize, pOutData, nMaxOutDataSize); if (nResult < 0 && pCompressor->flags & LZSA_FLAG_RAW_BLOCK) { nResult = lzsa_write_raw_uncompressed_block_v2(pCompressor, pInWindow, nPreviousBlockSize, nPreviousBlockSize + nInDataSize, pOutData, nMaxOutDataSize); } diff --git a/src/shrink_context.c b/src/shrink_context.c index f3ec444..74a7a16 100644 --- a/src/shrink_context.c +++ b/src/shrink_context.c @@ -58,7 +58,9 @@ int lzsa_compressor_init(lzsa_compressor *pCompressor, const int nMaxWindowSize, pCompressor->intervals = NULL; pCompressor->pos_data = NULL; pCompressor->open_intervals = NULL; + pCompressor->match = NULL; pCompressor->best_match = NULL; + pCompressor->improved_match = NULL; pCompressor->arrival = NULL; pCompressor->min_match_size = nMinMatchSize; if (pCompressor->min_match_size < nMinMatchSizeForFormat) @@ -93,7 +95,18 @@ int lzsa_compressor_init(lzsa_compressor *pCompressor, const int nMaxWindowSize, pCompressor->best_match = (lzsa_match *)malloc(nMaxWindowSize * sizeof(lzsa_match)); if (pCompressor->best_match) { - return 0; + if (pCompressor->format_version == 2) { + pCompressor->improved_match = (lzsa_match *)malloc(nMaxWindowSize * sizeof(lzsa_match)); + + if (pCompressor->improved_match) { + pCompressor->match = (lzsa_match *)malloc(nMaxWindowSize * 32 * sizeof(lzsa_match)); + if (pCompressor->match) + return 0; + } + } + else { + return 0; + } } } } @@ -113,6 +126,16 @@ int lzsa_compressor_init(lzsa_compressor *pCompressor, const int nMaxWindowSize, void lzsa_compressor_destroy(lzsa_compressor *pCompressor) { divsufsort_destroy(&pCompressor->divsufsort_context); + if (pCompressor->match) { + free(pCompressor->match); + pCompressor->match = NULL; + } + + if (pCompressor->improved_match) { + free(pCompressor->improved_match); + pCompressor->improved_match = NULL; + } + if (pCompressor->arrival) { free(pCompressor->arrival); pCompressor->arrival = NULL; diff --git a/src/shrink_context.h b/src/shrink_context.h index fd8176b..8e51822 100644 --- a/src/shrink_context.h +++ b/src/shrink_context.h @@ -74,6 +74,7 @@ typedef struct { unsigned short rep_offset; int num_literals; + int score; unsigned short match_offset; unsigned short match_len; @@ -114,7 +115,9 @@ typedef struct _lzsa_compressor { unsigned int *intervals; unsigned int *pos_data; unsigned int *open_intervals; + lzsa_match *match; lzsa_match *best_match; + lzsa_match *improved_match; lzsa_arrival *arrival; int min_match_size; int format_version;