Updates to Brotli compression format, decoder and encoder

This commit contains a batch of changes that were made to the Brotli
compression algorithm in the last month. Most important changes:

   * Format change: don't push distances representing static dictionary words to the distance cache.
   * Fix decoder invalid memory access bug caused by building a non-complete Huffman tree.
   * Add a mode parameter to the encoder interface.
   * Use different hashers for text and font mode.
   * Add a heuristics to the hasher for skipping non-compressible data.
   * Exhaustive search of static dictionary during backward reference search.
This commit is contained in:
Zoltan Szabadka
2014-03-20 14:32:35 +01:00
parent 7f848593bd
commit 278b89484f
14 changed files with 546 additions and 280 deletions
+175 -127
View File
@@ -168,15 +168,6 @@ void EncodeMetaBlockLength(size_t meta_block_size,
}
}
template<int kSize>
void EntropyEncode(int val, const EntropyCode<kSize>& code,
int* storage_ix, uint8_t* storage) {
if (code.count_ <= 1) {
return;
};
WriteBits(code.depth_[val], code.bits_[val], storage_ix, storage);
}
void StoreHuffmanTreeOfHuffmanTreeToBitMask(
const uint8_t* code_length_bitdepth,
int* storage_ix, uint8_t* storage) {
@@ -225,7 +216,9 @@ void StoreHuffmanTreeToBitMask(
for (int i = 0; i < huffman_tree_size; ++i) {
const int ix = huffman_tree[i];
const int extra_bits = huffman_tree_extra_bits[i];
EntropyEncode(ix, entropy, storage_ix, storage);
if (entropy.count_ > 1) {
WriteBits(entropy.depth_[ix], entropy.bits_[ix], storage_ix, storage);
}
switch (ix) {
case 16:
WriteBits(2, extra_bits, storage_ix, storage);
@@ -240,8 +233,7 @@ void StoreHuffmanTreeToBitMask(
template<int kSize>
void StoreHuffmanCodeSimple(
const EntropyCode<kSize>& code, int alphabet_size,
int max_bits,
int* storage_ix, uint8_t* storage) {
int max_bits, int* storage_ix, uint8_t* storage) {
const uint8_t *depth = &code.depth_[0];
int symbols[4];
// Quadratic sort.
@@ -304,37 +296,66 @@ void StoreHuffmanCodeComplex(
storage_ix, storage);
}
template<int kSize>
void StoreHuffmanCode(const EntropyCode<kSize>& code, int alphabet_size,
int* storage_ix, uint8_t* storage) {
void BuildAndStoreEntropyCode(const Histogram<kSize>& histogram,
const int tree_limit,
const int alphabet_size,
EntropyCode<kSize>* code,
int* storage_ix, uint8_t* storage) {
memset(code->depth_, 0, sizeof(code->depth_));
memset(code->bits_, 0, sizeof(code->bits_));
memset(code->symbols_, 0, sizeof(code->symbols_));
code->count_ = 0;
int max_bits_counter = alphabet_size - 1;
int max_bits = 0;
while (max_bits_counter) {
max_bits_counter >>= 1;
++max_bits;
}
if (code.count_ == 0) {
// Emit a minimal tree for empty cases.
// bits: small tree marker: 1, count-1: 0, max_bits-sized encoding for 0
WriteBits(4 + max_bits, 0x1, storage_ix, storage);
} else if (code.count_ <= 4) {
StoreHuffmanCodeSimple(
code, alphabet_size, max_bits,
storage_ix, storage);
for (size_t i = 0; i < alphabet_size; i++) {
if (histogram.data_[i] > 0) {
if (code->count_ < 4) code->symbols_[code->count_] = i;
++code->count_;
}
}
if (code->count_ <= 1) {
WriteBits(2, 1, storage_ix, storage);
WriteBits(2, 0, storage_ix, storage);
WriteBits(max_bits, code->symbols_[0], storage_ix, storage);
return;
}
if (alphabet_size >= 50 && code->count_ >= 16) {
std::vector<int> counts(alphabet_size);
memcpy(&counts[0], histogram.data_, sizeof(counts[0]) * alphabet_size);
OptimizeHuffmanCountsForRle(alphabet_size, &counts[0]);
CreateHuffmanTree(&counts[0], alphabet_size, tree_limit, code->depth_);
} else {
StoreHuffmanCodeComplex(
code, alphabet_size,
storage_ix, storage);
CreateHuffmanTree(histogram.data_, alphabet_size, tree_limit, code->depth_);
}
ConvertBitDepthsToSymbols(code->depth_, alphabet_size, code->bits_);
if (code->count_ <= 4) {
StoreHuffmanCodeSimple(*code, alphabet_size, max_bits, storage_ix, storage);
} else {
StoreHuffmanCodeComplex(*code, alphabet_size, storage_ix, storage);
}
}
template<int kSize>
void StoreHuffmanCodes(const std::vector<EntropyCode<kSize> >& codes,
int alphabet_size,
int* storage_ix, uint8_t* storage) {
for (int i = 0; i < codes.size(); ++i) {
StoreHuffmanCode(codes[i], alphabet_size, storage_ix, storage);
void BuildAndStoreEntropyCodes(
const std::vector<Histogram<kSize> >& histograms,
int alphabet_size,
std::vector<EntropyCode<kSize> >* entropy_codes,
int* storage_ix, uint8_t* storage) {
entropy_codes->resize(histograms.size());
for (int i = 0; i < histograms.size(); ++i) {
BuildAndStoreEntropyCode(histograms[i], 15, alphabet_size,
&(*entropy_codes)[i],
storage_ix, storage);
}
}
@@ -342,7 +363,7 @@ void EncodeCommand(const Command& cmd,
const EntropyCodeCommand& entropy,
int* storage_ix, uint8_t* storage) {
int code = cmd.command_prefix_;
EntropyEncode(code, entropy, storage_ix, storage);
WriteBits(entropy.depth_[code], entropy.bits_[code], storage_ix, storage);
if (code >= 128) {
code -= 128;
}
@@ -364,13 +385,15 @@ void EncodeCopyDistance(const Command& cmd, const EntropyCodeDistance& entropy,
int code = cmd.distance_prefix_;
int extra_bits = cmd.distance_extra_bits_;
uint64_t extra_bits_val = cmd.distance_extra_bits_value_;
EntropyEncode(code, entropy, storage_ix, storage);
WriteBits(entropy.depth_[code], entropy.bits_[code], storage_ix, storage);
if (extra_bits > 0) {
WriteBits(extra_bits, extra_bits_val, storage_ix, storage);
}
}
void ComputeDistanceShortCodes(std::vector<Command>* cmds,
size_t pos,
const size_t max_backward,
int* dist_ringbuffer,
size_t* ringbuffer_idx) {
static const int kIndexOffset[16] = {
@@ -380,30 +403,40 @@ void ComputeDistanceShortCodes(std::vector<Command>* cmds,
0, 0, 0, 0, -1, 1, -2, 2, -3, 3, -1, 1, -2, 2, -3, 3
};
for (int i = 0; i < cmds->size(); ++i) {
pos += (*cmds)[i].insert_length_;
size_t max_distance = std::min(pos, max_backward);
int cur_dist = (*cmds)[i].copy_distance_;
if (cur_dist == 0) break;
int dist_code = cur_dist + 16;
int limits[16] = { 0, 4, 10, 11,
6, 6, 11, 11,
11, 11, 11, 11,
12, 12, 12, 12 };
for (int k = 0; k < 16; ++k) {
// Only accept more popular choices.
if (cur_dist < limits[k]) {
// Typically unpopular ranges, don't replace a short distance
// with them.
continue;
if (cur_dist <= max_distance) {
if (cur_dist == 0) break;
int limits[16] = { 0, 0, 0, 0,
6, 6, 11, 11,
11, 11, 11, 11,
12, 12, 12, 12 };
for (int k = 0; k < 16; ++k) {
// Only accept more popular choices.
if (cur_dist < limits[k]) {
// Typically unpopular ranges, don't replace a short distance
// with them.
continue;
}
int comp = (dist_ringbuffer[(*ringbuffer_idx + kIndexOffset[k]) & 3] +
kValueOffset[k]);
if (cur_dist == comp) {
dist_code = k + 1;
break;
}
}
int comp = (dist_ringbuffer[(*ringbuffer_idx + kIndexOffset[k]) & 3] +
kValueOffset[k]);
if (cur_dist == comp) {
dist_code = k + 1;
break;
if (dist_code > 1) {
dist_ringbuffer[*ringbuffer_idx & 3] = cur_dist;
++(*ringbuffer_idx);
}
}
if (dist_code > 1) {
dist_ringbuffer[*ringbuffer_idx & 3] = cur_dist;
++(*ringbuffer_idx);
pos += (*cmds)[i].copy_length_;
} else {
int word_idx = cur_dist - max_distance - 1;
const std::string word =
GetTransformedDictionaryWord((*cmds)[i].copy_length_code_, word_idx);
pos += word.size();
}
(*cmds)[i].distance_code_ = dist_code;
}
@@ -558,18 +591,20 @@ void EncodeContextMap(const std::vector<int>& context_map,
for (int i = 0; i < rle_symbols.size(); ++i) {
symbol_histogram.Add(rle_symbols[i]);
}
EntropyCodeContextMap symbol_code;
BuildEntropyCode(symbol_histogram, 15, num_clusters + max_run_length_prefix,
&symbol_code);
bool use_rle = max_run_length_prefix > 0;
WriteBits(1, use_rle, storage_ix, storage);
if (use_rle) {
WriteBits(4, max_run_length_prefix - 1, storage_ix, storage);
}
StoreHuffmanCode(symbol_code, num_clusters + max_run_length_prefix,
storage_ix, storage);
EntropyCodeContextMap symbol_code;
BuildAndStoreEntropyCode(symbol_histogram, 15,
num_clusters + max_run_length_prefix,
&symbol_code,
storage_ix, storage);
for (int i = 0; i < rle_symbols.size(); ++i) {
EntropyEncode(rle_symbols[i], symbol_code, storage_ix, storage);
WriteBits(symbol_code.depth_[rle_symbols[i]],
symbol_code.bits_[rle_symbols[i]],
storage_ix, storage);
if (rle_symbols[i] > 0 && rle_symbols[i] <= max_run_length_prefix) {
WriteBits(rle_symbols[i], extra_bits[i], storage_ix, storage);
}
@@ -577,16 +612,6 @@ void EncodeContextMap(const std::vector<int>& context_map,
WriteBits(1, 1, storage_ix, storage); // use move-to-front
}
template<int kSize>
void BuildEntropyCodes(const std::vector<Histogram<kSize> >& histograms,
int alphabet_size,
std::vector<EntropyCode<kSize> >* entropy_codes) {
entropy_codes->resize(histograms.size());
for (int i = 0; i < histograms.size(); ++i) {
BuildEntropyCode(histograms[i], 15, alphabet_size, &(*entropy_codes)[i]);
}
}
struct BlockSplitCode {
EntropyCodeBlockType block_type_code;
EntropyCodeBlockLength block_len_code;
@@ -598,8 +623,8 @@ void EncodeBlockLength(const EntropyCodeBlockLength& entropy,
int len_code = BlockLengthPrefix(length);
int extra_bits = BlockLengthExtraBits(len_code);
int extra_bits_value = length - BlockLengthOffset(len_code);
EntropyEncode(len_code, entropy, storage_ix, storage);
WriteBits(entropy.depth_[len_code], entropy.bits_[len_code],
storage_ix, storage);
if (extra_bits > 0) {
WriteBits(extra_bits, extra_bits_value, storage_ix, storage);
}
@@ -632,26 +657,25 @@ void BuildAndEncodeBlockSplitCode(const BlockSplit& split,
BlockSplitCode* code,
int* storage_ix, uint8_t* storage) {
EncodeVarLenUint8(split.num_types_ - 1, storage_ix, storage);
if (split.num_types_ == 1) {
return;
}
HistogramBlockType type_histo;
for (int i = 0; i < split.type_codes_.size(); ++i) {
for (int i = 1; i < split.type_codes_.size(); ++i) {
type_histo.Add(split.type_codes_[i]);
}
BuildEntropyCode(type_histo, 15, split.num_types_ + 2,
&code->block_type_code);
HistogramBlockLength length_histo;
for (int i = 0; i < split.lengths_.size(); ++i) {
length_histo.Add(BlockLengthPrefix(split.lengths_[i]));
}
BuildEntropyCode(length_histo, 15, kNumBlockLenPrefixes,
&code->block_len_code);
StoreHuffmanCode(code->block_type_code, split.num_types_ + 2,
storage_ix, storage);
StoreHuffmanCode(code->block_len_code, kNumBlockLenPrefixes,
storage_ix, storage);
BuildAndStoreEntropyCode(type_histo, 15, split.num_types_ + 2,
&code->block_type_code,
storage_ix, storage);
BuildAndStoreEntropyCode(length_histo, 15, kNumBlockLenPrefixes,
&code->block_len_code,
storage_ix, storage);
EncodeBlockLength(code->block_len_code, split.lengths_[0],
storage_ix, storage);
}
@@ -664,7 +688,9 @@ void MoveAndEncode(const BlockSplitCode& code,
it->type_ = it->split_.types_[it->idx_];
it->length_ = it->split_.lengths_[it->idx_];
int type_code = it->split_.type_codes_[it->idx_];
EntropyEncode(type_code, code.block_type_code, storage_ix, storage);
WriteBits(code.block_type_code.depth_[type_code],
code.block_type_code.bits_[type_code],
storage_ix, storage);
EncodeBlockLength(code.block_len_code, it->length_, storage_ix, storage);
}
--it->length_;
@@ -773,10 +799,8 @@ void StoreMetaBlock(const MetaBlock& mb,
int* storage_ix, uint8_t* storage) {
size_t length = MetaBlockLength(mb.cmds);
const size_t end_pos = *pos + length;
EncodeMetaBlockLength(length,
is_last,
false,
storage_ix, storage);
EncodeMetaBlockLength(length, is_last, false, storage_ix, storage);
if (length == 0) {
return;
}
@@ -792,26 +816,27 @@ void StoreMetaBlock(const MetaBlock& mb,
WriteBits(2, mb.params.distance_postfix_bits, storage_ix, storage);
WriteBits(4,
mb.params.num_direct_distance_codes >>
mb.params.distance_postfix_bits, storage_ix, storage);
mb.params.distance_postfix_bits,
storage_ix, storage);
int num_distance_codes =
kNumDistanceShortCodes + mb.params.num_direct_distance_codes +
(48 << mb.params.distance_postfix_bits);
for (int i = 0; i < mb.literal_split.num_types_; ++i) {
WriteBits(2, mb.literal_context_modes[i], storage_ix, storage);
}
EncodeContextMap(mb.literal_context_map, mb.literal_histograms.size(), storage_ix, storage);
EncodeContextMap(mb.distance_context_map, mb.distance_histograms.size(), storage_ix, storage);
EncodeContextMap(mb.literal_context_map, mb.literal_histograms.size(),
storage_ix, storage);
EncodeContextMap(mb.distance_context_map, mb.distance_histograms.size(),
storage_ix, storage);
std::vector<EntropyCodeLiteral> literal_codes;
std::vector<EntropyCodeCommand> command_codes;
std::vector<EntropyCodeDistance> distance_codes;
BuildEntropyCodes(mb.literal_histograms, 256, &literal_codes);
BuildEntropyCodes(mb.command_histograms, kNumCommandPrefixes,
&command_codes);
BuildEntropyCodes(mb.distance_histograms, num_distance_codes,
&distance_codes);
StoreHuffmanCodes(literal_codes, 256, storage_ix, storage);
StoreHuffmanCodes(command_codes, kNumCommandPrefixes, storage_ix, storage);
StoreHuffmanCodes(distance_codes, num_distance_codes, storage_ix, storage);
BuildAndStoreEntropyCodes(mb.literal_histograms, 256, &literal_codes,
storage_ix, storage);
BuildAndStoreEntropyCodes(mb.command_histograms, kNumCommandPrefixes,
&command_codes, storage_ix, storage);
BuildAndStoreEntropyCodes(mb.distance_histograms, num_distance_codes,
&distance_codes, storage_ix, storage);
BlockSplitIterator literal_it(mb.literal_split);
BlockSplitIterator command_it(mb.command_split);
BlockSplitIterator distance_it(mb.distance_split);
@@ -828,8 +853,10 @@ void StoreMetaBlock(const MetaBlock& mb,
Context(prev_byte, prev_byte2,
mb.literal_context_modes[literal_it.type_]));
histogram_idx = mb.literal_context_map[context];
EntropyEncode(ringbuffer[*pos & mask],
literal_codes[histogram_idx], storage_ix, storage);
int literal = ringbuffer[*pos & mask];
WriteBits(literal_codes[histogram_idx].depth_[literal],
literal_codes[histogram_idx].bits_[literal],
storage_ix, storage);
++(*pos);
}
if (*pos < end_pos && cmd.distance_prefix_ != 0xffff) {
@@ -845,9 +872,10 @@ void StoreMetaBlock(const MetaBlock& mb,
}
}
BrotliCompressor::BrotliCompressor()
: window_bits_(kWindowBits),
hasher_(new Hasher),
BrotliCompressor::BrotliCompressor(BrotliParams params)
: params_(params),
window_bits_(kWindowBits),
hashers_(new Hashers()),
dist_ringbuffer_idx_(0),
input_pos_(0),
ringbuffer_(kRingBufferBits, kMetaBlockSizeBits),
@@ -859,28 +887,41 @@ BrotliCompressor::BrotliCompressor()
dist_ringbuffer_[2] = 11;
dist_ringbuffer_[3] = 4;
storage_[0] = 0;
StoreDictionaryWordHashes();
switch (params.mode) {
case BrotliParams::MODE_TEXT: hash_type_ = Hashers::HASH_15_8_4; break;
case BrotliParams::MODE_FONT: hash_type_ = Hashers::HASH_15_8_2; break;
default: break;
}
hashers_->Init(hash_type_);
if (params.mode == BrotliParams::MODE_TEXT) {
StoreDictionaryWordHashes();
}
}
BrotliCompressor::~BrotliCompressor() {
delete hasher_;
delete[] storage_;
}
StaticDictionary *BrotliCompressor::static_dictionary_ = NULL;
void BrotliCompressor::StoreDictionaryWordHashes() {
for (int t = kNumTransforms - 1; t >= 0; --t) {
for (int i = kMaxDictionaryWordLength; i >= 3; --i) {
const int num_words = 1 << kBrotliDictionarySizeBitsByLength[i];
for (int j = num_words - 1; j >= 0; --j) {
int word_id = t * num_words + j;
std::string word = GetTransformedDictionaryWord(i, word_id);
if (word.size() >= 3) {
hasher_->Store(reinterpret_cast<const uint8_t*>(&word[0]),
(-1) * ((i << 20) + word_id + 1));
const int num_transforms = kNumTransforms;
if (static_dictionary_ == NULL) {
static_dictionary_ = new StaticDictionary;
for (int t = num_transforms - 1; t >= 0; --t) {
for (int i = kMaxDictionaryWordLength; i >= 3; --i) {
const int num_words = 1 << kBrotliDictionarySizeBitsByLength[i];
for (int j = num_words - 1; j >= 0; --j) {
int word_id = t * num_words + j;
std::string word = GetTransformedDictionaryWord(i, word_id);
if (word.size() >= 3) {
static_dictionary_->Insert(word, i, word_id);
}
}
}
}
}
hashers_->SetStaticDictionary(static_dictionary_);
}
void BrotliCompressor::WriteStreamHeader() {
@@ -908,25 +949,30 @@ void BrotliCompressor::WriteMetaBlock(const size_t input_size,
input_size, kMinUTF8Ratio);
if (utf8_mode) {
EstimateBitCostsForLiteralsUTF8(input_pos_, input_size,
kRingBufferMask, ringbuffer_.start(),
&literal_cost_[0]);
kRingBufferMask, kRingBufferMask,
ringbuffer_.start(), &literal_cost_[0]);
} else {
EstimateBitCostsForLiterals(input_pos_, input_size,
kRingBufferMask, ringbuffer_.start(),
&literal_cost_[0]);
kRingBufferMask, kRingBufferMask,
ringbuffer_.start(), &literal_cost_[0]);
}
CreateBackwardReferences(input_size, input_pos_,
ringbuffer_.start(),
&literal_cost_[0],
kRingBufferMask, kMaxBackwardDistance,
hasher_,
&commands);
ComputeDistanceShortCodes(&commands, dist_ringbuffer_,
CreateBackwardReferences(
input_size, input_pos_,
ringbuffer_.start(),
&literal_cost_[0],
kRingBufferMask, kMaxBackwardDistance,
hashers_.get(),
hash_type_,
&commands);
ComputeDistanceShortCodes(&commands, input_pos_, kMaxBackwardDistance,
dist_ringbuffer_,
&dist_ringbuffer_idx_);
}
EncodingParams params;
params.num_direct_distance_codes = 12;
params.distance_postfix_bits = 1;
params.num_direct_distance_codes =
params_.mode == BrotliParams::MODE_FONT ? 12 : 0;
params.distance_postfix_bits =
params_.mode == BrotliParams::MODE_FONT ? 1 : 0;
params.literal_context_mode = CONTEXT_SIGNED;
const int storage_ix0 = storage_ix_;
MetaBlock mb;
@@ -935,6 +981,7 @@ void BrotliCompressor::WriteMetaBlock(const size_t input_size,
StoreMetaBlock(mb, is_last, ringbuffer_.start(), kRingBufferMask,
&input_pos_, &storage_ix_, storage_);
size_t output_size = is_last ? ((storage_ix_ + 7) >> 3) : (storage_ix_ >> 3);
output_size -= (storage_ix0 >> 3);
if (input_size + 4 < output_size) {
storage_ix_ = storage_ix0;
storage_[storage_ix_ >> 3] &= (1 << (storage_ix_ & 7)) - 1;
@@ -968,7 +1015,8 @@ void BrotliCompressor::FinishStream(
}
int BrotliCompressBuffer(size_t input_size,
int BrotliCompressBuffer(BrotliParams params,
size_t input_size,
const uint8_t* input_buffer,
size_t* encoded_size,
uint8_t* encoded_buffer) {
@@ -978,7 +1026,7 @@ int BrotliCompressBuffer(size_t input_size,
return 1;
}
BrotliCompressor compressor;
BrotliCompressor compressor(params);
compressor.WriteStreamHeader();
const int max_block_size = 1 << kMetaBlockSizeBits;