Add a faster but less dense compression mode.

The new mode can be used by setting the greedy_block_split
field of BrotliParams to true.

This commit moves all the meta-block processing code
into its own library and moves the meta-block encoding
code to brotli_bit_stream.cc from encode.cc
This commit is contained in:
Zoltan Szabadka 2015-03-27 14:20:35 +01:00
parent 169c32d887
commit 534654def1
10 changed files with 705 additions and 292 deletions

View File

@ -2,7 +2,7 @@
include ../shared.mk
OBJS = backward_references.o block_splitter.o brotli_bit_stream.o encode.o entropy_encode.o histogram.o literal_cost.o
OBJS = backward_references.o block_splitter.o brotli_bit_stream.o encode.o entropy_encode.o histogram.o literal_cost.o metablock.o
all : $(OBJS)

View File

@ -24,6 +24,31 @@
namespace brotli {
static inline double BitsEntropy(const int *population, int size) {
int sum = 0;
double retval = 0;
const int *population_end = population + size;
int p;
if (size & 1) {
goto odd_number_of_elements_left;
}
while (population < population_end) {
p = *population++;
sum += p;
retval -= p * FastLog2(p);
odd_number_of_elements_left:
p = *population++;
sum += p;
retval -= p * FastLog2(p);
}
if (sum) retval -= sum * log(sum);
if (retval < sum) {
// At least one bit per literal is needed.
retval = sum;
}
return retval;
}
static const int kHuffmanExtraBits[kCodeLengthCodes] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3,
};

View File

@ -284,21 +284,21 @@ void ClusterBlocks(const DataType* data, const size_t length,
void BuildBlockSplit(const std::vector<uint8_t>& block_ids, BlockSplit* split) {
int cur_id = block_ids[0];
int cur_length = 1;
split->num_types_ = -1;
split->num_types = -1;
for (int i = 1; i < block_ids.size(); ++i) {
if (block_ids[i] != cur_id) {
split->types_.push_back(cur_id);
split->lengths_.push_back(cur_length);
split->num_types_ = std::max(split->num_types_, cur_id);
split->types.push_back(cur_id);
split->lengths.push_back(cur_length);
split->num_types = std::max(split->num_types, cur_id);
cur_id = block_ids[i];
cur_length = 0;
}
++cur_length;
}
split->types_.push_back(cur_id);
split->lengths_.push_back(cur_length);
split->num_types_ = std::max(split->num_types_, cur_id);
++split->num_types_;
split->types.push_back(cur_id);
split->lengths.push_back(cur_length);
split->num_types = std::max(split->num_types, cur_id);
++split->num_types;
}
template<typename HistogramType, typename DataType>
@ -309,12 +309,12 @@ void SplitByteVector(const std::vector<DataType>& data,
const double block_switch_cost,
BlockSplit* split) {
if (data.empty()) {
split->num_types_ = 1;
split->num_types = 1;
return;
} else if (data.size() < kMinLengthForBlockSplitting) {
split->num_types_ = 1;
split->types_.push_back(0);
split->lengths_.push_back(data.size());
split->num_types = 1;
split->types.push_back(0);
split->lengths.push_back(data.size());
return;
}
std::vector<HistogramType> histograms;
@ -356,7 +356,6 @@ void SplitBlock(const std::vector<Command>& cmds,
&insert_and_copy_codes,
&distance_prefixes);
SplitByteVector<HistogramLiteral>(
literals,
kSymbolsPerLiteralHistogram, kMaxLiteralHistograms,

View File

@ -24,28 +24,23 @@
#include <utility>
#include "./command.h"
#include "./metablock.h"
namespace brotli {
struct BlockSplit {
int num_types_;
std::vector<int> types_;
std::vector<int> lengths_;
};
struct BlockSplitIterator {
explicit BlockSplitIterator(const BlockSplit& split)
: split_(split), idx_(0), type_(0), length_(0) {
if (!split.lengths_.empty()) {
length_ = split.lengths_[0];
if (!split.lengths.empty()) {
length_ = split.lengths[0];
}
}
void Next() {
if (length_ == 0) {
++idx_;
type_ = split_.types_[idx_];
length_ = split_.lengths_[idx_];
type_ = split_.types[idx_];
length_ = split_.lengths[idx_];
}
--length_;
}

View File

@ -23,6 +23,7 @@
#include <vector>
#include "./bit_cost.h"
#include "./context.h"
#include "./entropy_encode.h"
#include "./fast_log.h"
#include "./prefix.h"
@ -572,4 +573,253 @@ void StoreTrivialContextMap(int num_types,
}
}
// Manages the encoding of one block category (literal, command or distance).
class BlockEncoder {
public:
BlockEncoder(int alphabet_size,
int num_block_types,
const std::vector<int>& block_types,
const std::vector<int>& block_lengths)
: alphabet_size_(alphabet_size),
num_block_types_(num_block_types),
block_types_(block_types),
block_lengths_(block_lengths),
block_ix_(0),
block_len_(block_lengths.empty() ? 0 : block_lengths[0]),
entropy_ix_(0) {}
// Creates entropy codes of block lengths and block types and stores them
// to the bit stream.
void BuildAndStoreBlockSwitchEntropyCodes(int quality,
int* storage_ix, uint8_t* storage) {
BuildAndStoreBlockSplitCode(
block_types_, block_lengths_, num_block_types_,
quality, &block_split_code_, storage_ix, storage);
}
// Creates entropy codes for all block types and stores them to the bit
// stream.
template<int kSize>
void BuildAndStoreEntropyCodes(
const std::vector<Histogram<kSize> >& histograms,
int quality,
int* storage_ix, uint8_t* storage) {
depths_.resize(histograms.size() * alphabet_size_);
bits_.resize(histograms.size() * alphabet_size_);
for (int i = 0; i < histograms.size(); ++i) {
int ix = i * alphabet_size_;
BuildAndStoreHuffmanTree(&histograms[i].data_[0], alphabet_size_,
quality,
&depths_[ix], &bits_[ix],
storage_ix, storage);
}
}
// Stores the next symbol with the entropy code of the current block type.
// Updates the block type and block length at block boundaries.
void StoreSymbol(int symbol, int* storage_ix, uint8_t* storage) {
if (block_len_ == 0) {
++block_ix_;
block_len_ = block_lengths_[block_ix_];
entropy_ix_ = block_types_[block_ix_] * alphabet_size_;
StoreBlockSwitch(block_split_code_, block_ix_, storage_ix, storage);
}
--block_len_;
int ix = entropy_ix_ + symbol;
WriteBits(depths_[ix], bits_[ix], storage_ix, storage);
}
// Stores the next symbol with the entropy code of the current block type and
// context value.
// Updates the block type and block length at block boundaries.
template<int kContextBits>
void StoreSymbolWithContext(int symbol, int context,
const std::vector<int>& context_map,
int* storage_ix, uint8_t* storage) {
if (block_len_ == 0) {
++block_ix_;
block_len_ = block_lengths_[block_ix_];
entropy_ix_ = block_types_[block_ix_] << kContextBits;
StoreBlockSwitch(block_split_code_, block_ix_, storage_ix, storage);
}
--block_len_;
int histo_ix = context_map[entropy_ix_ + context];
int ix = histo_ix * alphabet_size_ + symbol;
WriteBits(depths_[ix], bits_[ix], storage_ix, storage);
}
private:
const int alphabet_size_;
const int num_block_types_;
const std::vector<int>& block_types_;
const std::vector<int>& block_lengths_;
BlockSplitCode block_split_code_;
int block_ix_;
int block_len_;
int entropy_ix_;
std::vector<uint8_t> depths_;
std::vector<uint16_t> bits_;
};
bool StoreMetaBlock(const uint8_t* input,
size_t start_pos,
size_t length,
size_t mask,
bool is_last,
int quality,
int num_direct_distance_codes,
int distance_postfix_bits,
int literal_context_mode,
const brotli::Command *commands,
size_t n_commands,
const MetaBlockSplit& mb,
int *storage_ix,
uint8_t *storage) {
if (!StoreCompressedMetaBlockHeader(is_last, length, storage_ix, storage)) {
return false;
}
if (length == 0) {
return true;
}
int num_distance_codes =
kNumDistanceShortCodes + num_direct_distance_codes +
(48 << distance_postfix_bits);
BlockEncoder literal_enc(256,
mb.literal_split.num_types,
mb.literal_split.types,
mb.literal_split.lengths);
BlockEncoder command_enc(kNumCommandPrefixes,
mb.command_split.num_types,
mb.command_split.types,
mb.command_split.lengths);
BlockEncoder distance_enc(num_distance_codes,
mb.distance_split.num_types,
mb.distance_split.types,
mb.distance_split.lengths);
literal_enc.BuildAndStoreBlockSwitchEntropyCodes(
quality, storage_ix, storage);
command_enc.BuildAndStoreBlockSwitchEntropyCodes(
quality, storage_ix, storage);
distance_enc.BuildAndStoreBlockSwitchEntropyCodes(
quality, storage_ix, storage);
WriteBits(2, distance_postfix_bits, storage_ix, storage);
WriteBits(4, num_direct_distance_codes >> distance_postfix_bits,
storage_ix, storage);
for (int i = 0; i < mb.literal_split.num_types; ++i) {
WriteBits(2, literal_context_mode, storage_ix, storage);
}
if (mb.literal_context_map.empty()) {
StoreTrivialContextMap(mb.literal_histograms.size(), kLiteralContextBits,
storage_ix, storage);
} else {
EncodeContextMap(mb.literal_context_map, mb.literal_histograms.size(),
storage_ix, storage);
}
if (mb.distance_context_map.empty()) {
StoreTrivialContextMap(mb.distance_histograms.size(), kDistanceContextBits,
storage_ix, storage);
} else {
EncodeContextMap(mb.distance_context_map, mb.distance_histograms.size(),
storage_ix, storage);
}
literal_enc.BuildAndStoreEntropyCodes(mb.literal_histograms, quality,
storage_ix, storage);
command_enc.BuildAndStoreEntropyCodes(mb.command_histograms, quality,
storage_ix, storage);
distance_enc.BuildAndStoreEntropyCodes(mb.distance_histograms, quality,
storage_ix, storage);
size_t pos = start_pos;
for (int i = 0; i < n_commands; ++i) {
const Command cmd = commands[i];
int cmd_code = cmd.cmd_prefix_;
int lennumextra = cmd.cmd_extra_ >> 48;
uint64_t lenextra = cmd.cmd_extra_ & 0xffffffffffffULL;
command_enc.StoreSymbol(cmd_code, storage_ix, storage);
WriteBits(lennumextra, lenextra, storage_ix, storage);
if (mb.literal_context_map.empty()) {
for (int j = 0; j < cmd.insert_len_; j++) {
literal_enc.StoreSymbol(input[pos & mask], storage_ix, storage);
++pos;
}
} else {
for (int j = 0; j < cmd.insert_len_; ++j) {
uint8_t prev_byte = pos > 0 ? input[(pos - 1) & mask] : 0;
uint8_t prev_byte2 = pos > 1 ? input[(pos - 2) & mask] : 0;
int context = Context(prev_byte, prev_byte2,
literal_context_mode);
int literal = input[pos & mask];
literal_enc.StoreSymbolWithContext<kLiteralContextBits>(
literal, context, mb.literal_context_map, storage_ix, storage);
++pos;
}
}
if (cmd.copy_len_ > 0 && cmd.cmd_prefix_ >= 128) {
int dist_code = cmd.dist_prefix_;
int distnumextra = cmd.dist_extra_ >> 24;
int distextra = cmd.dist_extra_ & 0xffffff;
if (mb.distance_context_map.empty()) {
distance_enc.StoreSymbol(dist_code, storage_ix, storage);
} else {
int context = cmd.DistanceContext();
distance_enc.StoreSymbolWithContext<kDistanceContextBits>(
dist_code, context, mb.distance_context_map, storage_ix, storage);
}
brotli::WriteBits(distnumextra, distextra, storage_ix, storage);
}
pos += cmd.copy_len_;
}
return true;
}
// This is for storing uncompressed blocks (simple raw storage of
// bytes-as-bytes).
bool StoreUncompressedMetaBlock(bool final_block,
const uint8_t * __restrict input,
size_t position, size_t mask,
size_t len,
int * __restrict storage_ix,
uint8_t * __restrict storage) {
if (!brotli::StoreUncompressedMetaBlockHeader(len, storage_ix, storage)) {
return false;
}
*storage_ix = ((*storage_ix + 7) / 8) * 8; // Go to next byte
size_t masked_pos = position & mask;
if (masked_pos + len > mask + 1) {
size_t len1 = mask + 1 - masked_pos;
memcpy(&storage[*storage_ix >> 3], &input[masked_pos], len1);
*storage_ix += len1 << 3;
len -= len1;
masked_pos = 0;
}
memcpy(&storage[*storage_ix >> 3], &input[masked_pos], len);
*storage_ix += len << 3;
// We need to clear the next 4 bytes to continue to be
// compatible with WriteBits.
brotli::WriteBitsPrepareStorage(*storage_ix, storage);
// Since the uncomressed block itself may not be the final block, add an empty
// one after this.
if (final_block) {
brotli::WriteBits(1, 1, storage_ix, storage); // islast
brotli::WriteBits(1, 1, storage_ix, storage); // isempty
*storage_ix = ((*storage_ix + 7) / 8) * 8; // Go to next byte
// We need to clear the next 4 bytes to continue to be
// compatible with WriteBits.
brotli::WriteBitsPrepareStorage(*storage_ix, storage);
}
return true;
}
} // namespace brotli

View File

@ -28,6 +28,8 @@
#include <stdint.h>
#include <vector>
#include "./metablock.h"
namespace brotli {
// All Store functions here will use a storage_ix, which is always the bit
@ -104,6 +106,30 @@ void StoreBlockSwitch(const BlockSplitCode& code,
int* storage_ix,
uint8_t* storage);
bool StoreMetaBlock(const uint8_t* input,
size_t start_pos,
size_t length,
size_t mask,
bool final_block,
int quality,
int num_direct_distance_codes,
int distance_postfix_bits,
int literal_context_mode,
const brotli::Command *commands,
size_t n_commands,
const MetaBlockSplit& mb,
int *storage_ix,
uint8_t *storage);
// This is for storing uncompressed blocks (simple raw storage of
// bytes-as-bytes).
bool StoreUncompressedMetaBlock(bool final_block,
const uint8_t* input,
size_t position, size_t mask,
size_t len,
int* storage_ix,
uint8_t* storage);
} // namespace brotli
#endif // BROTLI_ENC_BROTLI_BIT_STREAM_H_

View File

@ -25,6 +25,7 @@
#include "./brotli_bit_stream.h"
#include "./cluster.h"
#include "./context.h"
#include "./metablock.h"
#include "./transform.h"
#include "./entropy_encode.h"
#include "./fast_log.h"
@ -48,24 +49,6 @@ static const int kMetaBlockSizeBits = 21;
static const int kRingBufferBits = 23;
static const int kRingBufferMask = (1 << kRingBufferBits) - 1;
template<int kSize>
double Entropy(const std::vector<Histogram<kSize> >& histograms) {
double retval = 0;
for (int i = 0; i < histograms.size(); ++i) {
retval += histograms[i].EntropyBitCost();
}
return retval;
}
template<int kSize>
double TotalBitCost(const std::vector<Histogram<kSize> >& histograms) {
double retval = 0;
for (int i = 0; i < histograms.size(); ++i) {
retval += PopulationCost(histograms[i]);
}
return retval;
}
int ParseAsUTF8(int* symbol, const uint8_t* input, int size) {
// ASCII
if ((input[0] & 0x80) == 0) {
@ -128,55 +111,6 @@ bool IsMostlyUTF8(const uint8_t* data, size_t length, double min_fraction) {
return size_utf8 > min_fraction * length;
}
template<int kSize>
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_));
BuildAndStoreHuffmanTree(histogram.data_, alphabet_size, 9,
code->depth_, code->bits_, storage_ix, storage);
}
template<int kSize>
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);
}
}
void EncodeCommand(const Command& cmd,
const EntropyCodeCommand& entropy,
int* storage_ix, uint8_t* storage) {
int code = cmd.cmd_prefix_;
WriteBits(entropy.depth_[code], entropy.bits_[code], storage_ix, storage);
int nextra = cmd.cmd_extra_ >> 48;
uint64_t extra = cmd.cmd_extra_ & 0xffffffffffffULL;
if (nextra > 0) {
WriteBits(nextra, extra, storage_ix, storage);
}
}
void EncodeCopyDistance(const Command& cmd, const EntropyCodeDistance& entropy,
int* storage_ix, uint8_t* storage) {
int code = cmd.dist_prefix_;
int extra_bits = cmd.dist_extra_ >> 24;
uint64_t extra_bits_val = cmd.dist_extra_ & 0xffffff;
WriteBits(entropy.depth_[code], entropy.bits_[code], storage_ix, storage);
if (extra_bits > 0) {
WriteBits(extra_bits, extra_bits_val, storage_ix, storage);
}
}
void RecomputeDistancePrefixes(std::vector<Command>* cmds,
int num_direct_distance_codes,
int distance_postfix_bits) {
@ -196,101 +130,6 @@ void RecomputeDistancePrefixes(std::vector<Command>* cmds,
}
}
void MoveAndEncode(const BlockSplitCode& code,
BlockSplitIterator* it,
int* storage_ix, uint8_t* storage) {
if (it->length_ == 0) {
++it->idx_;
it->type_ = it->split_.types_[it->idx_];
it->length_ = it->split_.lengths_[it->idx_];
StoreBlockSwitch(code, it->idx_, storage_ix, storage);
}
--it->length_;
}
struct EncodingParams {
int num_direct_distance_codes;
int distance_postfix_bits;
int literal_context_mode;
};
struct MetaBlock {
std::vector<Command> cmds;
EncodingParams params;
BlockSplit literal_split;
BlockSplit command_split;
BlockSplit distance_split;
std::vector<int> literal_context_modes;
std::vector<int> literal_context_map;
std::vector<int> distance_context_map;
std::vector<HistogramLiteral> literal_histograms;
std::vector<HistogramCommand> command_histograms;
std::vector<HistogramDistance> distance_histograms;
};
void BuildMetaBlock(const EncodingParams& params,
const std::vector<Command>& cmds,
const uint8_t* ringbuffer,
const size_t pos,
const size_t mask,
MetaBlock* mb) {
mb->cmds = cmds;
mb->params = params;
if (cmds.empty()) {
return;
}
RecomputeDistancePrefixes(&mb->cmds,
mb->params.num_direct_distance_codes,
mb->params.distance_postfix_bits);
SplitBlock(mb->cmds,
&ringbuffer[pos & mask],
&mb->literal_split,
&mb->command_split,
&mb->distance_split);
mb->literal_context_modes.resize(mb->literal_split.num_types_,
mb->params.literal_context_mode);
int num_literal_contexts =
mb->literal_split.num_types_ << kLiteralContextBits;
int num_distance_contexts =
mb->distance_split.num_types_ << kDistanceContextBits;
std::vector<HistogramLiteral> literal_histograms(num_literal_contexts);
mb->command_histograms.resize(mb->command_split.num_types_);
std::vector<HistogramDistance> distance_histograms(num_distance_contexts);
BuildHistograms(mb->cmds,
mb->literal_split,
mb->command_split,
mb->distance_split,
ringbuffer,
pos,
mask,
mb->literal_context_modes,
&literal_histograms,
&mb->command_histograms,
&distance_histograms);
// Histogram ids need to fit in one byte.
static const int kMaxNumberOfHistograms = 256;
mb->literal_histograms = literal_histograms;
ClusterHistograms(literal_histograms,
1 << kLiteralContextBits,
mb->literal_split.num_types_,
kMaxNumberOfHistograms,
&mb->literal_histograms,
&mb->literal_context_map);
mb->distance_histograms = distance_histograms;
ClusterHistograms(distance_histograms,
1 << kDistanceContextBits,
mb->distance_split.num_types_,
kMaxNumberOfHistograms,
&mb->distance_histograms,
&mb->distance_context_map);
}
size_t MetaBlockLength(const std::vector<Command>& cmds) {
size_t length = 0;
for (int i = 0; i < cmds.size(); ++i) {
@ -300,100 +139,6 @@ size_t MetaBlockLength(const std::vector<Command>& cmds) {
return length;
}
bool StoreMetaBlock(const MetaBlock& mb,
const bool is_last,
const uint8_t* ringbuffer,
const size_t mask,
size_t* pos,
int* storage_ix, uint8_t* storage) {
size_t length = MetaBlockLength(mb.cmds);
const size_t end_pos = *pos + length;
if (!StoreCompressedMetaBlockHeader(is_last, length, storage_ix, storage)) {
return false;
}
if (length == 0) {
return true;
}
BlockSplitCode literal_split_code;
BlockSplitCode command_split_code;
BlockSplitCode distance_split_code;
BuildAndStoreBlockSplitCode(mb.literal_split.types_,
mb.literal_split.lengths_,
mb.literal_split.num_types_,
9, // quality
&literal_split_code,
storage_ix, storage);
BuildAndStoreBlockSplitCode(mb.command_split.types_,
mb.command_split.lengths_,
mb.command_split.num_types_,
9, // quality
&command_split_code,
storage_ix, storage);
BuildAndStoreBlockSplitCode(mb.distance_split.types_,
mb.distance_split.lengths_,
mb.distance_split.num_types_,
9, // quality
&distance_split_code,
storage_ix, storage);
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);
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);
std::vector<EntropyCodeLiteral> literal_codes;
std::vector<EntropyCodeCommand> command_codes;
std::vector<EntropyCodeDistance> distance_codes;
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);
for (int i = 0; i < mb.cmds.size(); ++i) {
const Command& cmd = mb.cmds[i];
MoveAndEncode(command_split_code, &command_it, storage_ix, storage);
EncodeCommand(cmd, command_codes[command_it.type_], storage_ix, storage);
for (int j = 0; j < cmd.insert_len_; ++j) {
MoveAndEncode(literal_split_code, &literal_it, storage_ix, storage);
int histogram_idx = literal_it.type_;
uint8_t prev_byte = *pos > 0 ? ringbuffer[(*pos - 1) & mask] : 0;
uint8_t prev_byte2 = *pos > 1 ? ringbuffer[(*pos - 2) & mask] : 0;
int context = ((literal_it.type_ << kLiteralContextBits) +
Context(prev_byte, prev_byte2,
mb.literal_context_modes[literal_it.type_]));
histogram_idx = mb.literal_context_map[context];
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.cmd_prefix_ >= 128) {
MoveAndEncode(distance_split_code, &distance_it, storage_ix, storage);
int context = (distance_it.type_ << 2) + cmd.DistanceContext();
int histogram_index = mb.distance_context_map[context];
EncodeCopyDistance(cmd, distance_codes[histogram_index],
storage_ix, storage);
}
*pos += cmd.copy_len_;
}
return true;
}
BrotliCompressor::BrotliCompressor(BrotliParams params)
: params_(params),
window_bits_(kWindowBits),
@ -499,20 +244,40 @@ bool BrotliCompressor::WriteMetaBlock(const size_t input_size,
commands.push_back(Command(last_insert_len));
}
}
EncodingParams params;
params.num_direct_distance_codes =
int 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;
int distance_postfix_bits = params_.mode == BrotliParams::MODE_FONT ? 1 : 0;
int literal_context_mode = CONTEXT_SIGNED;
const int storage_ix0 = storage_ix_;
MetaBlock mb;
BuildMetaBlock(params, commands, ringbuffer_.start(), input_pos_,
kRingBufferMask, &mb);
if (!StoreMetaBlock(mb, is_last, ringbuffer_.start(), kRingBufferMask,
&input_pos_, &storage_ix_, storage_)) {
MetaBlockSplit mb;
size_t len = MetaBlockLength(commands);
if (!commands.empty()) {
if (params_.greedy_block_split) {
BuildMetaBlockGreedy(ringbuffer_.start(), input_pos_, kRingBufferMask,
commands.data(), commands.size(), 9, &mb);
} else {
RecomputeDistancePrefixes(&commands,
num_direct_distance_codes,
distance_postfix_bits);
BuildMetaBlock(ringbuffer_.start(), input_pos_, kRingBufferMask,
commands,
num_direct_distance_codes,
distance_postfix_bits,
literal_context_mode,
&mb);
}
}
if (!StoreMetaBlock(ringbuffer_.start(), input_pos_, len, kRingBufferMask,
is_last, 9,
num_direct_distance_codes,
distance_postfix_bits,
literal_context_mode,
commands.data(), commands.size(),
mb,
&storage_ix_, storage_)) {
return false;
}
input_pos_ += len;
size_t output_size = is_last ? ((storage_ix_ + 7) >> 3) : (storage_ix_ >> 3);
output_size -= (storage_ix0 >> 3);
if (input_size + 4 < output_size) {

View File

@ -28,6 +28,11 @@
namespace brotli {
struct BrotliParams {
BrotliParams()
: mode(MODE_TEXT),
enable_transforms(false),
greedy_block_split(false) {}
enum Mode {
MODE_TEXT = 0,
MODE_FONT = 1,
@ -35,8 +40,7 @@ struct BrotliParams {
Mode mode;
bool enable_transforms;
BrotliParams() : mode(MODE_TEXT), enable_transforms(false) {}
bool greedy_block_split;
};
class BrotliCompressor {

283
enc/metablock.cc Normal file
View File

@ -0,0 +1,283 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Algorithms for distributing the literals and commands of a metablock between
// block types and contexts.
#include "./metablock.h"
#include "./block_splitter.h"
#include "./cluster.h"
#include "./histogram.h"
namespace brotli {
void BuildMetaBlock(const uint8_t* ringbuffer,
const size_t pos,
const size_t mask,
const std::vector<Command>& cmds,
int num_direct_distance_codes,
int distance_postfix_bits,
int literal_context_mode,
MetaBlockSplit* mb) {
SplitBlock(cmds,
&ringbuffer[pos & mask],
&mb->literal_split,
&mb->command_split,
&mb->distance_split);
std::vector<int> literal_context_modes(mb->literal_split.num_types,
literal_context_mode);
int num_literal_contexts =
mb->literal_split.num_types << kLiteralContextBits;
int num_distance_contexts =
mb->distance_split.num_types << kDistanceContextBits;
std::vector<HistogramLiteral> literal_histograms(num_literal_contexts);
mb->command_histograms.resize(mb->command_split.num_types);
std::vector<HistogramDistance> distance_histograms(num_distance_contexts);
BuildHistograms(cmds,
mb->literal_split,
mb->command_split,
mb->distance_split,
ringbuffer,
pos,
mask,
literal_context_modes,
&literal_histograms,
&mb->command_histograms,
&distance_histograms);
// Histogram ids need to fit in one byte.
static const int kMaxNumberOfHistograms = 256;
mb->literal_histograms = literal_histograms;
ClusterHistograms(literal_histograms,
1 << kLiteralContextBits,
mb->literal_split.num_types,
kMaxNumberOfHistograms,
&mb->literal_histograms,
&mb->literal_context_map);
mb->distance_histograms = distance_histograms;
ClusterHistograms(distance_histograms,
1 << kDistanceContextBits,
mb->distance_split.num_types,
kMaxNumberOfHistograms,
&mb->distance_histograms,
&mb->distance_context_map);
}
// Greedy block splitter for one block category (literal, command or distance).
template<typename HistogramType>
class BlockSplitter {
public:
BlockSplitter(int alphabet_size,
int min_block_size,
double split_threshold,
int num_symbols,
int quality,
BlockSplit* split,
std::vector<HistogramType>* histograms)
: alphabet_size_(alphabet_size),
min_block_size_(min_block_size),
split_threshold_(split_threshold),
quality_(quality),
num_blocks_(0),
split_(split),
histograms_(histograms),
target_block_size_(min_block_size),
block_size_(0),
curr_histogram_ix_(0),
merge_last_count_(0) {
int max_num_blocks = num_symbols / min_block_size + 1;
// We have to allocate one more histogram than the maximum number of block
// types for the current histogram when the meta-block is too big.
int max_num_types = std::min(max_num_blocks, kMaxBlockTypes + 1);
split_->lengths.resize(max_num_blocks);
split_->types.resize(max_num_blocks);
histograms_->resize(max_num_types);
last_histogram_ix_[0] = last_histogram_ix_[1] = 0;
}
// Adds the next symbol to the current histogram. When the current histogram
// reaches the target size, decides on merging the block.
void AddSymbol(int symbol) {
(*histograms_)[curr_histogram_ix_].Add(symbol);
++block_size_;
if (block_size_ == target_block_size_) {
FinishBlock(/* is_final = */ false);
}
}
// Does either of three things:
// (1) emits the current block with a new block type;
// (2) emits the current block with the type of the second last block;
// (3) merges the current block with the last block.
void FinishBlock(bool is_final) {
if (block_size_ < min_block_size_) {
block_size_ = min_block_size_;
}
if (num_blocks_ == 0) {
// Create first block.
split_->lengths[0] = block_size_;
split_->types[0] = 0;
last_entropy_[0] =
BitsEntropy(&(*histograms_)[0].data_[0], alphabet_size_);
last_entropy_[1] = last_entropy_[0];
++num_blocks_;
++split_->num_types;
++curr_histogram_ix_;
block_size_ = 0;
} else if (block_size_ > 0) {
double entropy = BitsEntropy(&(*histograms_)[curr_histogram_ix_].data_[0],
alphabet_size_);
HistogramType combined_histo[2];
double combined_entropy[2];
double diff[2];
for (int j = 0; j < 2; ++j) {
int last_histogram_ix = last_histogram_ix_[j];
combined_histo[j] = (*histograms_)[curr_histogram_ix_];
combined_histo[j].AddHistogram((*histograms_)[last_histogram_ix]);
combined_entropy[j] = BitsEntropy(
&combined_histo[j].data_[0], alphabet_size_);
diff[j] = combined_entropy[j] - entropy - last_entropy_[j];
}
if (split_->num_types < kMaxBlockTypes &&
diff[0] > split_threshold_ &&
diff[1] > split_threshold_) {
// Create new block.
split_->lengths[num_blocks_] = block_size_;
split_->types[num_blocks_] = split_->num_types;
last_histogram_ix_[1] = last_histogram_ix_[0];
last_histogram_ix_[0] = split_->num_types;
last_entropy_[1] = last_entropy_[0];
last_entropy_[0] = entropy;
++num_blocks_;
++split_->num_types;
++curr_histogram_ix_;
block_size_ = 0;
merge_last_count_ = 0;
target_block_size_ = min_block_size_;
} else if (diff[1] < diff[0] - 20.0) {
// Combine this block with second last block.
split_->lengths[num_blocks_] = block_size_;
split_->types[num_blocks_] = split_->types[num_blocks_ - 2];
std::swap(last_histogram_ix_[0], last_histogram_ix_[1]);
(*histograms_)[last_histogram_ix_[0]] = combined_histo[1];
last_entropy_[1] = last_entropy_[0];
last_entropy_[0] = combined_entropy[1];
++num_blocks_;
block_size_ = 0;
(*histograms_)[curr_histogram_ix_].Clear();
merge_last_count_ = 0;
target_block_size_ = min_block_size_;
} else {
// Combine this block with last block.
split_->lengths[num_blocks_ - 1] += block_size_;
(*histograms_)[last_histogram_ix_[0]] = combined_histo[0];
last_entropy_[0] = combined_entropy[0];
if (split_->num_types == 1) {
last_entropy_[1] = last_entropy_[0];
}
block_size_ = 0;
(*histograms_)[curr_histogram_ix_].Clear();
if (++merge_last_count_ > 1) {
target_block_size_ += min_block_size_;
}
}
}
if (is_final) {
(*histograms_).resize(split_->num_types);
split_->types.resize(num_blocks_);
split_->lengths.resize(num_blocks_);
}
}
private:
static const int kMaxBlockTypes = 256;
// Alphabet size of particular block category.
const int alphabet_size_;
// We collect at least this many symbols for each block.
const int min_block_size_;
// We merge histograms A and B if
// entropy(A+B) < entropy(A) + entropy(B) + split_threshold_,
// where A is the current histogram and B is the histogram of the last or the
// second last block type.
const double split_threshold_;
// Quality setting used for speed vs. compression ratio decisions.
const int quality_;
int num_blocks_;
BlockSplit* split_; // not owned
std::vector<HistogramType>* histograms_; // not owned
// The number of symbols that we want to collect before deciding on whether
// or not to merge the block with a previous one or emit a new block.
int target_block_size_;
// The number of symbols in the current histogram.
int block_size_;
// Offset of the current histogram.
int curr_histogram_ix_;
// Offset of the histograms of the previous two block types.
int last_histogram_ix_[2];
// Entropy of the previous two block types.
double last_entropy_[2];
// The number of times we merged the current block with the last one.
int merge_last_count_;
};
void BuildMetaBlockGreedy(const uint8_t* ringbuffer,
size_t pos,
size_t mask,
const Command *commands,
size_t n_commands,
int quality,
MetaBlockSplit* mb) {
int num_literals = 0;
for (int i = 0; i < n_commands; ++i) {
num_literals += commands[i].insert_len_;
}
BlockSplitter<HistogramLiteral> lit_blocks(
256, 512, 400.0, num_literals, quality,
&mb->literal_split, &mb->literal_histograms);
BlockSplitter<HistogramCommand> cmd_blocks(
kNumCommandPrefixes, 1024, 500.0, n_commands, quality,
&mb->command_split, &mb->command_histograms);
BlockSplitter<HistogramDistance> dist_blocks(
64, 512, 100.0, n_commands, quality,
&mb->distance_split, &mb->distance_histograms);
for (int i = 0; i < n_commands; ++i) {
const Command cmd = commands[i];
cmd_blocks.AddSymbol(cmd.cmd_prefix_);
for (int j = 0; j < cmd.insert_len_; ++j) {
lit_blocks.AddSymbol(ringbuffer[pos & mask]);
++pos;
}
pos += cmd.copy_len_;
if (cmd.copy_len_ > 0 && cmd.cmd_prefix_ >= 128) {
dist_blocks.AddSymbol(cmd.dist_prefix_);
}
}
lit_blocks.FinishBlock(/* is_final = */ true);
cmd_blocks.FinishBlock(/* is_final = */ true);
dist_blocks.FinishBlock(/* is_final = */ true);
}
} // namespace brotli

66
enc/metablock.h Normal file
View File

@ -0,0 +1,66 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Algorithms for distributing the literals and commands of a metablock between
// block types and contexts.
#ifndef BROTLI_ENC_METABLOCK_H_
#define BROTLI_ENC_METABLOCK_H_
#include <vector>
#include "./command.h"
#include "./histogram.h"
namespace brotli {
struct BlockSplit {
BlockSplit() : num_types(0) {}
int num_types;
std::vector<int> types;
std::vector<int> lengths;
};
struct MetaBlockSplit {
BlockSplit literal_split;
BlockSplit command_split;
BlockSplit distance_split;
std::vector<int> literal_context_map;
std::vector<int> distance_context_map;
std::vector<HistogramLiteral> literal_histograms;
std::vector<HistogramCommand> command_histograms;
std::vector<HistogramDistance> distance_histograms;
};
void BuildMetaBlock(const uint8_t* ringbuffer,
const size_t pos,
const size_t mask,
const std::vector<Command>& cmds,
int num_direct_distance_codes,
int distance_postfix_bits,
int literal_context_mode,
MetaBlockSplit* mb);
void BuildMetaBlockGreedy(const uint8_t* ringbuffer,
size_t pos,
size_t mask,
const Command *commands,
size_t n_commands,
int quality,
MetaBlockSplit* mb);
} // namespace brotli
#endif // BROTLI_ENC_METABLOCK_H_