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 three weeks. Most important changes:

  * Added UTF8 context model for good text compression.
  * Simplified context modeling by having only 4 context modes.
  * Per-block context mode selection.
  * Faster backward copying and bit reading functions.
  * More efficient histogram coding.
  * Streaming support for the decoder and encoder.
This commit is contained in:
Zoltan Szabadka
2013-11-15 19:02:17 +01:00
parent 79e99afe46
commit 1571db36a9
23 changed files with 1647 additions and 870 deletions
+30 -29
View File
@@ -20,60 +20,64 @@
#include <vector>
#include "./command.h"
#include "./hash.h"
#include "./literal_cost.h"
namespace brotli {
void CreateBackwardReferences(const uint8_t* data,
int length,
void CreateBackwardReferences(size_t num_bytes,
size_t position,
const uint8_t* ringbuffer,
const float* literal_cost,
size_t ringbuffer_mask,
const size_t max_backward_limit,
Hasher* hasher,
std::vector<Command>* commands) {
HashLongestMatch<13,11> *hasher = new HashLongestMatch<13,11>;
float *literal_cost = new float[length];
EstimateBitCostsForLiterals(length, data, literal_cost);
hasher->SetLiteralCost(literal_cost);
// Length heuristic that seems to help probably by better selection
// of lazy matches of similar lengths.
int insert_length = 0;
size_t i = 0;
size_t i = position & ringbuffer_mask;
const int i_diff = position - i;
const size_t i_end = i + num_bytes;
double average_cost = 0.0;
for (int i = 0; i < length; ++i) {
average_cost += literal_cost[i];
for (int k = position; k < position + num_bytes; ++k) {
average_cost += literal_cost[k & ringbuffer_mask];
}
average_cost /= length;
average_cost /= num_bytes;
hasher->set_average_cost(average_cost);
while (i + 2 < length) {
while (i + 2 < i_end) {
size_t best_len = 0;
size_t best_dist = 0;
double best_score = 0;
const size_t max_distance = std::min(i, 1UL << 24);
const size_t max_distance = std::min(i + i_diff, max_backward_limit);
hasher->set_insert_length(insert_length);
bool match_found = hasher->FindLongestMatch(
data, i, length - i, max_distance,
ringbuffer, literal_cost, ringbuffer_mask,
i + i_diff, i_end - i, max_distance,
&best_len, &best_dist, &best_score);
if (match_found) {
// Found a match. Let's look for something even better ahead.
int delayed_backward_references_in_row = 0;
while (i + 4 < length &&
while (i + 4 < i_end &&
delayed_backward_references_in_row < 4) {
size_t best_len_2 = 0;
size_t best_dist_2 = 0;
double best_score_2 = 0;
hasher->Store(data + i, i);
hasher->Store(ringbuffer + i, i + i_diff);
match_found = hasher->FindLongestMatch(
data, i + 1, length - i - 1, max_distance,
ringbuffer, literal_cost, ringbuffer_mask,
i + i_diff + 1, i_end - i - 1, max_distance,
&best_len_2, &best_dist_2, &best_score_2);
double cost_diff_lazy = 0;
if (best_len >= 4) {
cost_diff_lazy += hasher->literal_cost(i + 4) - average_cost;
cost_diff_lazy +=
literal_cost[(i + 4) & ringbuffer_mask] - average_cost;
}
{
const int tail_length = best_len_2 - best_len + 1;
for (int k = 0; k < tail_length; ++k) {
cost_diff_lazy -= hasher->literal_cost(i + best_len + k) -
cost_diff_lazy -=
literal_cost[(i + best_len + k) & ringbuffer_mask] -
average_cost;
}
}
@@ -84,7 +88,7 @@ void CreateBackwardReferences(const uint8_t* data,
}
// Add bias to slightly avoid lazy matching.
cost_diff_lazy += 2.0 + delayed_backward_references_in_row * 0.2;
cost_diff_lazy += 0.04 * hasher->literal_cost(i);
cost_diff_lazy += 0.04 * literal_cost[i & ringbuffer_mask];
if (match_found && best_score_2 >= best_score + cost_diff_lazy) {
// Ok, let's just write one byte for now and start a match from the
@@ -109,18 +113,18 @@ void CreateBackwardReferences(const uint8_t* data,
insert_length = 0;
++i;
for (int j = 1; j < best_len; ++j) {
if (i + 2 < length) {
hasher->Store(data + i, i);
if (i + 2 < i_end) {
hasher->Store(ringbuffer + i, i + i_diff);
}
++i;
}
} else {
++insert_length;
hasher->Store(data + i, i);
hasher->Store(ringbuffer + i, i + i_diff);
++i;
}
}
insert_length += (length - i);
insert_length += (i_end - i);
if (insert_length > 0) {
Command cmd;
@@ -129,9 +133,6 @@ void CreateBackwardReferences(const uint8_t* data,
cmd.copy_distance_ = 0;
commands->push_back(cmd);
}
delete[] literal_cost;
delete hasher;
}
} // namespace brotli
+8 -2
View File
@@ -20,12 +20,18 @@
#include <stdint.h>
#include <vector>
#include "./hash.h"
#include "./command.h"
namespace brotli {
void CreateBackwardReferences(const uint8_t* data,
int length,
void CreateBackwardReferences(size_t num_bytes,
size_t position,
const uint8_t* ringbuffer,
const float* literal_cost,
size_t ringbuffer_mask,
const size_t max_backward_limit,
Hasher* hasher,
std::vector<Command>* commands);
} // namespace brotli
+13 -8
View File
@@ -122,26 +122,31 @@ static inline int HuffmanBitCost(const uint8_t* depth, int length) {
template<int kSize>
double PopulationCost(const Histogram<kSize>& histogram) {
if (histogram.total_count_ == 0) {
return 4;
return 11;
}
int symbols[2] = { 0 };
int count = 0;
for (int i = 0; i < kSize && count < 3; ++i) {
for (int i = 0; i < kSize && count < 5; ++i) {
if (histogram.data_[i] > 0) {
if (count < 2) symbols[count] = i;
++count;
}
}
if (count <= 2 && symbols[0] < 256 && symbols[1] < 256) {
return ((symbols[0] <= 1 ? 4 : 11) +
(count == 2 ? 8 + histogram.total_count_ : 0));
if (count == 1) {
return 11;
}
if (count == 2) {
return 19 + histogram.total_count_;
}
uint8_t depth[kSize] = { 0 };
CreateHuffmanTree(&histogram.data_[0], kSize, 15, depth);
int bits = HuffmanBitCost(depth, kSize);
int bits = 0;
for (int i = 0; i < kSize; ++i) {
bits += histogram.data_[i] * depth[i];
}
if (count == 3) {
bits += 27;
} else {
bits += HuffmanBitCost(depth, kSize);
}
return bits;
}
+127 -72
View File
@@ -21,25 +21,124 @@
namespace brotli {
static const int kSigned2BitContextLookup[] = {
// Second-order context lookup table for UTF8 byte streams.
//
// If p1 and p2 are the previous two bytes, we calcualte the context as
//
// context = kUTF8ContextLookup[p1] | kUTF8ContextLookup[p2 + 256].
//
// If the previous two bytes are ASCII characters (i.e. < 128), this will be
// equivalent to
//
// context = 4 * context1(p1) + context2(p2),
//
// where context1 is based on the previous byte in the following way:
//
// 0 : non-ASCII control
// 1 : \t, \n, \r
// 2 : space
// 3 : other punctuation
// 4 : " '
// 5 : %
// 6 : ( < [ {
// 7 : ) > ] }
// 8 : , ; :
// 9 : .
// 10 : =
// 11 : number
// 12 : upper-case vowel
// 13 : upper-case consonant
// 14 : lower-case vowel
// 15 : lower-case consonant
//
// and context2 is based on the second last byte:
//
// 0 : control, space
// 1 : punctuation
// 2 : upper-case letter, number
// 3 : lower-case letter
//
// If the last byte is ASCII, and the second last byte is not (in a valid UTF8
// stream it will be a continuation byte, value between 128 and 191), the
// context is the same as if the second last byte was an ASCII control or space.
//
// If the last byte is a UTF8 lead byte (value >= 192), then the next byte will
// be a continuation byte and the context id is 2 or 3 depending on the LSB of
// the last byte and to a lesser extent on the second last byte if it is ASCII.
//
// If the last byte is a UTF8 continuation byte, the second last byte can be:
// - continuation byte: the next byte is probably ASCII or lead byte (assuming
// 4-byte UTF8 characters are rare) and the context id is 0 or 1.
// - lead byte (192 - 207): next byte is ASCII or lead byte, context is 0 or 1
// - lead byte (208 - 255): next byte is continuation byte, context is 2 or 3
//
// The possible value combinations of the previous two bytes, the range of
// context ids and the type of the next byte is summarized in the table below:
//
// |--------\-----------------------------------------------------------------|
// | \ Last byte |
// | Second \---------------------------------------------------------------|
// | last byte \ ASCII | cont. byte | lead byte |
// | \ (0-127) | (128-191) | (192-) |
// |=============|===================|=====================|==================|
// | ASCII | next: ASCII/lead | not valid | next: cont. |
// | (0-127) | context: 4 - 63 | | context: 2 - 3 |
// |-------------|-------------------|---------------------|------------------|
// | cont. byte | next: ASCII/lead | next: ASCII/lead | next: cont. |
// | (128-191) | context: 4 - 63 | context: 0 - 1 | context: 2 - 3 |
// |-------------|-------------------|---------------------|------------------|
// | lead byte | not valid | next: ASCII/lead | not valid |
// | (192-207) | | context: 0 - 1 | |
// |-------------|-------------------|---------------------|------------------|
// | lead byte | not valid | next: cont. | not valid |
// | (208-) | | context: 2 - 3 | |
// |-------------|-------------------|---------------------|------------------|
static const uint8_t kUTF8ContextLookup[512] = {
// Last byte.
//
// ASCII range.
0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 0, 4, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
8, 12, 16, 12, 12, 20, 12, 16, 24, 28, 12, 12, 32, 12, 36, 12,
44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 32, 32, 24, 40, 28, 12,
12, 48, 52, 52, 52, 48, 52, 52, 52, 48, 52, 52, 52, 52, 52, 48,
52, 52, 52, 52, 52, 48, 52, 52, 52, 52, 52, 24, 12, 28, 12, 12,
12, 56, 60, 60, 60, 56, 60, 60, 60, 56, 60, 60, 60, 60, 60, 56,
60, 60, 60, 60, 60, 56, 60, 60, 60, 60, 60, 24, 12, 28, 12, 0,
// UTF8 continuation byte range.
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
// UTF8 lead byte range.
2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3,
2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3,
2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3,
2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3,
// Second last byte.
//
// ASCII range.
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1,
1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1,
1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 0,
// UTF8 continuation byte range.
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// UTF8 lead byte range.
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3,
};
// Context lookup table for small signed integers.
static const int kSigned3BitContextLookup[] = {
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
@@ -59,69 +158,25 @@ static const int kSigned3BitContextLookup[] = {
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7,
};
static const int kSigned4BitContextLookup[] = {
0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 14, 15,
};
enum ContextType {
CONTEXT_NONE = 0,
CONTEXT_FULL = 1,
CONTEXT_MSB7 = 2,
CONTEXT_MSB6 = 3,
CONTEXT_MSB5 = 4,
CONTEXT_MSB4 = 5,
CONTEXT_MSB3 = 6,
CONTEXT_MSB2 = 7,
CONTEXT_MSB1 = 8,
CONTEXT_IS_ZERO = 9,
CONTEXT_SIGNED_2BIT = 10,
CONTEXT_SIGNED_3BIT = 11,
CONTEXT_SIGNED_4BIT = 12,
CONTEXT_SIGNED_MIXED_3BYTE = 13,
CONTEXT_LSB6 = 0,
CONTEXT_MSB6 = 1,
CONTEXT_UTF8 = 2,
CONTEXT_SIGNED = 3
};
static const int kContextSize[] = {
1, 256, 128, 64, 32, 16, 8, 4, 2, 2, 4, 8, 16, 64,
};
static inline int NumContexts(int mode) {
return kContextSize[mode];
}
static inline uint8_t Context(uint8_t prev_byte, uint8_t prev_byte2,
uint8_t prev_byte3, int mode) {
static inline uint8_t Context(uint8_t p1, uint8_t p2, int mode) {
switch (mode) {
case CONTEXT_NONE:
return 0;
case CONTEXT_IS_ZERO:
return prev_byte == 0 ? 0 : 1;
case CONTEXT_SIGNED_2BIT:
return kSigned2BitContextLookup[prev_byte];
case CONTEXT_SIGNED_3BIT:
return kSigned3BitContextLookup[prev_byte];
case CONTEXT_SIGNED_4BIT:
return kSigned4BitContextLookup[prev_byte];
case CONTEXT_SIGNED_MIXED_3BYTE:
return ((kSigned3BitContextLookup[prev_byte] << 3) +
(kSigned2BitContextLookup[prev_byte2] << 1) +
(prev_byte3 == 0 ? 0 : 1));
case CONTEXT_LSB6:
return p1 & 0x3f;
case CONTEXT_MSB6:
return p1 >> 2;
case CONTEXT_UTF8:
return kUTF8ContextLookup[p1] | kUTF8ContextLookup[p2 + 256];
case CONTEXT_SIGNED:
return (kSigned3BitContextLookup[p1] << 3) + kSigned3BitContextLookup[p2];
default:
return prev_byte >> (mode - 1);
return 0;
}
}
+232 -139
View File
@@ -26,7 +26,9 @@
#include "./context.h"
#include "./entropy_encode.h"
#include "./fast_log.h"
#include "./hash.h"
#include "./histogram.h"
#include "./literal_cost.h"
#include "./prefix.h"
#include "./write_bits.h"
@@ -41,31 +43,39 @@ double Entropy(const std::vector<Histogram<kSize> >& histograms) {
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;
}
void EncodeSize(size_t len, int* storage_ix, uint8_t* storage) {
std::vector<uint8_t> len_bytes;
while (len > 0) {
do {
len_bytes.push_back(len & 0xff);
len >>= 8;
};
} while (len > 0);
WriteBits(3, len_bytes.size(), storage_ix, storage);
for (int i = 0; i < len_bytes.size(); ++i) {
WriteBits(8, len_bytes[i], storage_ix, storage);
}
}
void EncodeMetaBlockLength(int input_size_bits,
size_t meta_block_size,
bool is_last_meta_block,
void EncodeMetaBlockLength(size_t meta_block_size,
int* storage_ix, uint8_t* storage) {
WriteBits(1, is_last_meta_block, storage_ix, storage);
if (is_last_meta_block) return;
while (input_size_bits > 0) {
WriteBits(8, meta_block_size & 0xff, storage_ix, storage);
meta_block_size >>= 8;
input_size_bits -= 8;
WriteBits(1, 0, storage_ix, storage);
int num_bits = Log2Floor(meta_block_size) + 1;
WriteBits(3, (num_bits + 3) >> 2, storage_ix, storage);
while (num_bits > 0) {
WriteBits(4, meta_block_size & 0xf, storage_ix, storage);
meta_block_size >>= 4;
num_bits -= 4;
}
if (input_size_bits > 0) {
WriteBits(input_size_bits, meta_block_size, storage_ix, storage);
if (num_bits > 0) {
WriteBits(num_bits, meta_block_size, storage_ix, storage);
}
}
@@ -82,7 +92,7 @@ void StoreHuffmanTreeOfHuffmanTreeToBitMask(
const uint8_t* code_length_bitdepth,
int* storage_ix, uint8_t* storage) {
static const uint8_t kStorageOrder[kCodeLengthCodes] = {
17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
1, 2, 3, 4, 0, 17, 18, 5, 6, 16, 7, 8, 9, 10, 11, 12, 13, 14, 15
};
// Throw away trailing zeros:
int codes_to_store = kCodeLengthCodes;
@@ -92,8 +102,16 @@ void StoreHuffmanTreeOfHuffmanTreeToBitMask(
}
}
WriteBits(4, codes_to_store - 4, storage_ix, storage);
for (int i = 0; i < codes_to_store; ++i) {
WriteBits(3, code_length_bitdepth[kStorageOrder[i]], storage_ix, storage);
const int skip_two_first =
code_length_bitdepth[kStorageOrder[0]] == 0 &&
code_length_bitdepth[kStorageOrder[1]] == 0;
WriteBits(1, skip_two_first, storage_ix, storage);
for (int i = skip_two_first * 2; i < codes_to_store; ++i) {
uint8_t len[] = { 2, 4, 3, 2, 2, 4 };
uint8_t bits[] = { 0, 7, 3, 1, 2, 15 };
int v = code_length_bitdepth[kStorageOrder[i]];
WriteBits(len[v], bits[v], storage_ix, storage);
}
}
@@ -124,30 +142,49 @@ void StoreHuffmanTreeToBitMask(
template<int kSize>
void StoreHuffmanCode(const EntropyCode<kSize>& code, int alphabet_size,
int* storage_ix, uint8_t* storage) {
const int kMaxBits = 8;
const int kMaxSymbol = 1 << kMaxBits;
const uint8_t *depth = &code.depth_[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 minimal tree for empty cases
// bits: small tree marker: 1, count-1: 0, large 8-bit code: 0, code: 0
WriteBits(4, 0x01, storage_ix, storage);
// bits: small tree marker: 1, count-1: 0, max_bits-sized encoding for 0
WriteBits(3 + max_bits, 0x01, storage_ix, storage);
return;
}
if (code.count_ <= 2 &&
code.symbols_[0] < kMaxSymbol &&
code.symbols_[1] < kMaxSymbol) {
// Small tree marker to encode 1 or 2 symbols.
WriteBits(1, 1, storage_ix, storage);
WriteBits(1, code.count_ - 1, storage_ix, storage);
if (code.symbols_[0] <= 1) {
// Code bit for small (1 bit) symbol value.
WriteBits(1, 0, storage_ix, storage);
WriteBits(1, code.symbols_[0], storage_ix, storage);
} else {
WriteBits(1, 1, storage_ix, storage);
WriteBits(8, code.symbols_[0], storage_ix, storage);
if (code.count_ <= 4) {
int symbols[4];
// Quadratic sort.
int k, j;
for (k = 0; k < code.count_; ++k) {
symbols[k] = code.symbols_[k];
}
if (code.count_ == 2) {
WriteBits(8, code.symbols_[1], storage_ix, storage);
for (k = 0; k < code.count_; ++k) {
for (j = k + 1; j < code.count_; ++j) {
if (depth[symbols[j]] < depth[symbols[k]]) {
int t = symbols[k];
symbols[k] = symbols[j];
symbols[j] = t;
}
}
}
// Small tree marker to encode 1-4 symbols.
WriteBits(1, 1, storage_ix, storage);
WriteBits(2, code.count_ - 1, storage_ix, storage);
for (int i = 0; i < code.count_; ++i) {
WriteBits(max_bits, symbols[i], storage_ix, storage);
}
if (code.count_ == 4) {
if (depth[symbols[0]] == 2 &&
depth[symbols[1]] == 2 &&
depth[symbols[2]] == 2 &&
depth[symbols[3]] == 2) {
WriteBits(1, 0, storage_ix, storage);
} else {
WriteBits(1, 1, storage_ix, storage);
}
}
return;
}
@@ -156,7 +193,7 @@ void StoreHuffmanCode(const EntropyCode<kSize>& code, int alphabet_size,
uint8_t huffman_tree[kSize];
uint8_t huffman_tree_extra_bits[kSize];
int huffman_tree_size = 0;
WriteHuffmanTree(&code.depth_[0],
WriteHuffmanTree(depth,
alphabet_size,
&huffman_tree[0],
&huffman_tree_extra_bits[0],
@@ -167,7 +204,7 @@ void StoreHuffmanCode(const EntropyCode<kSize>& code, int alphabet_size,
huffman_tree_histogram.Add(huffman_tree[i]);
}
EntropyCode<kCodeLengthCodes> huffman_tree_entropy;
BuildEntropyCode(huffman_tree_histogram, 7, kCodeLengthCodes,
BuildEntropyCode(huffman_tree_histogram, 5, kCodeLengthCodes,
&huffman_tree_entropy);
Histogram<kCodeLengthCodes> trimmed_histogram = huffman_tree_histogram;
uint8_t* last_code = &huffman_tree[huffman_tree_size - 1];
@@ -178,7 +215,7 @@ void StoreHuffmanCode(const EntropyCode<kSize>& code, int alphabet_size,
bool write_length = false;
if (trimmed_size > 1 && trimmed_size < huffman_tree_size) {
EntropyCode<kCodeLengthCodes> trimmed_entropy;
BuildEntropyCode(trimmed_histogram, 7, kCodeLengthCodes, &trimmed_entropy);
BuildEntropyCode(trimmed_histogram, 5, kCodeLengthCodes, &trimmed_entropy);
int huffman_bit_cost = HuffmanTreeBitCost(huffman_tree_histogram,
huffman_tree_entropy);
int trimmed_bit_cost = HuffmanTreeBitCost(trimmed_histogram,
@@ -247,16 +284,15 @@ void EncodeCopyDistance(const Command& cmd, const EntropyCodeDistance& entropy,
}
}
void ComputeDistanceShortCodes(std::vector<Command>* cmds) {
void ComputeDistanceShortCodes(std::vector<Command>* cmds,
int* dist_ringbuffer,
size_t* ringbuffer_idx) {
static const int kIndexOffset[16] = {
3, 2, 1, 0, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2
};
static const int kValueOffset[16] = {
0, 0, 0, 0, -1, 1, -2, 2, -3, 3, -1, 1, -2, 2, -3, 3
};
int dist_ringbuffer[4] = { 4, 11, 15, 16 };
int ringbuffer_idx = 0;
for (int i = 0; i < cmds->size(); ++i) {
int cur_dist = (*cmds)[i].copy_distance_;
if (cur_dist == 0) break;
@@ -268,7 +304,7 @@ void ComputeDistanceShortCodes(std::vector<Command>* cmds) {
// with them.
continue;
}
int comp = (dist_ringbuffer[(ringbuffer_idx + kIndexOffset[k]) & 3] +
int comp = (dist_ringbuffer[(*ringbuffer_idx + kIndexOffset[k]) & 3] +
kValueOffset[k]);
if (cur_dist == comp) {
dist_code = k + 1;
@@ -276,8 +312,8 @@ void ComputeDistanceShortCodes(std::vector<Command>* cmds) {
}
}
if (dist_code > 1) {
dist_ringbuffer[ringbuffer_idx & 3] = cur_dist;
++ringbuffer_idx;
dist_ringbuffer[*ringbuffer_idx & 3] = cur_dist;
++(*ringbuffer_idx);
}
(*cmds)[i].distance_code_ = dist_code;
}
@@ -414,19 +450,8 @@ int BestMaxZeroRunLengthPrefix(const std::vector<int>& v) {
}
void EncodeContextMap(const std::vector<int>& context_map,
int context_mode,
int context_mode_bits,
int num_clusters,
int* storage_ix, uint8_t* storage) {
if (context_mode == 0) {
WriteBits(1, 0, storage_ix, storage); // no context
return;
}
WriteBits(1, 1, storage_ix, storage); // have context
if (context_mode_bits > 0) {
WriteBits(context_mode_bits, context_mode - 1, storage_ix, storage);
}
WriteBits(8, num_clusters - 1, storage_ix, storage);
if (num_clusters == 1 || num_clusters == context_map.size()) {
@@ -560,7 +585,6 @@ struct EncodingParams {
int num_direct_distance_codes;
int distance_postfix_bits;
int literal_context_mode;
int distance_context_mode;
};
struct MetaBlock {
@@ -569,6 +593,7 @@ struct MetaBlock {
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;
@@ -578,8 +603,9 @@ struct MetaBlock {
void BuildMetaBlock(const EncodingParams& params,
const std::vector<Command>& cmds,
const uint8_t* input_buffer,
size_t pos,
const uint8_t* ringbuffer,
const size_t pos,
const size_t mask,
MetaBlock* mb) {
mb->cmds = cmds;
mb->params = params;
@@ -587,7 +613,7 @@ void BuildMetaBlock(const EncodingParams& params,
mb->params.num_direct_distance_codes,
mb->params.distance_postfix_bits);
SplitBlock(mb->cmds,
input_buffer + pos,
&ringbuffer[pos & mask],
&mb->literal_split,
&mb->command_split,
&mb->distance_split);
@@ -595,16 +621,14 @@ void BuildMetaBlock(const EncodingParams& params,
ComputeBlockTypeShortCodes(&mb->command_split);
ComputeBlockTypeShortCodes(&mb->distance_split);
int num_literal_contexts_per_block_type =
NumContexts(mb->params.literal_context_mode);
mb->literal_context_modes.resize(mb->literal_split.num_types_,
mb->params.literal_context_mode);
int num_literal_contexts =
mb->literal_split.num_types_ *
num_literal_contexts_per_block_type;
int num_distance_contexts_per_block_type =
(mb->params.distance_context_mode > 0 ? 4 : 1);
mb->literal_split.num_types_ << kLiteralContextBits;
int num_distance_contexts =
mb->distance_split.num_types_ *
num_distance_contexts_per_block_type;
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);
@@ -612,10 +636,10 @@ void BuildMetaBlock(const EncodingParams& params,
mb->literal_split,
mb->command_split,
mb->distance_split,
input_buffer,
ringbuffer,
pos,
mb->params.literal_context_mode,
mb->params.distance_context_mode,
mask,
mb->literal_context_modes,
&literal_histograms,
&mb->command_histograms,
&distance_histograms);
@@ -625,24 +649,20 @@ void BuildMetaBlock(const EncodingParams& params,
static const int kMaxNumberOfHistograms = 240;
mb->literal_histograms = literal_histograms;
if (mb->params.literal_context_mode > 0) {
ClusterHistograms(literal_histograms,
num_literal_contexts_per_block_type,
mb->literal_split.num_types_,
kMaxNumberOfHistograms,
&mb->literal_histograms,
&mb->literal_context_map);
}
ClusterHistograms(literal_histograms,
1 << kLiteralContextBits,
mb->literal_split.num_types_,
kMaxNumberOfHistograms,
&mb->literal_histograms,
&mb->literal_context_map);
mb->distance_histograms = distance_histograms;
if (mb->params.distance_context_mode > 0) {
ClusterHistograms(distance_histograms,
num_distance_contexts_per_block_type,
mb->distance_split.num_types_,
kMaxNumberOfHistograms,
&mb->distance_histograms,
&mb->distance_context_map);
}
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) {
@@ -655,14 +675,13 @@ size_t MetaBlockLength(const std::vector<Command>& cmds) {
}
void StoreMetaBlock(const MetaBlock& mb,
const uint8_t* input_buffer,
int input_size_bits,
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;
EncodeMetaBlockLength(input_size_bits, length - 1, is_last,
EncodeMetaBlockLength(length - 1,
storage_ix, storage);
BlockSplitCode literal_split_code;
BlockSplitCode command_split_code;
@@ -680,10 +699,11 @@ void StoreMetaBlock(const MetaBlock& mb,
int num_distance_codes =
kNumDistanceShortCodes + mb.params.num_direct_distance_codes +
(48 << mb.params.distance_postfix_bits);
EncodeContextMap(mb.literal_context_map, mb.params.literal_context_mode, 4,
mb.literal_histograms.size(), storage_ix, storage);
EncodeContextMap(mb.distance_context_map, mb.params.distance_context_mode, 0,
mb.distance_histograms.size(), storage_ix, storage);
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;
@@ -705,27 +725,22 @@ void StoreMetaBlock(const MetaBlock& mb,
for (int j = 0; j < cmd.insert_length_; ++j) {
MoveAndEncode(literal_split_code, &literal_it, storage_ix, storage);
int histogram_idx = literal_it.type_;
if (mb.params.literal_context_mode > 0) {
uint8_t prev_byte = *pos > 0 ? input_buffer[*pos - 1] : 0;
uint8_t prev_byte2 = *pos > 1 ? input_buffer[*pos - 2] : 0;
uint8_t prev_byte3 = *pos > 2 ? input_buffer[*pos - 3] : 0;
int context = (literal_it.type_ *
NumContexts(mb.params.literal_context_mode) +
Context(prev_byte, prev_byte2, prev_byte3,
mb.params.literal_context_mode));
histogram_idx = mb.literal_context_map[context];
}
EntropyEncode(input_buffer[(*pos)++],
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];
EntropyEncode(ringbuffer[*pos & mask],
literal_codes[histogram_idx], storage_ix, storage);
++(*pos);
}
if (*pos < end_pos && cmd.distance_prefix_ != 0xffff) {
MoveAndEncode(distance_split_code, &distance_it, storage_ix, storage);
int histogram_index = distance_it.type_;
if (mb.params.distance_context_mode > 0) {
int context = distance_it.type_ << 2;
context += (cmd.copy_length_ > 4) ? 3 : cmd.copy_length_ - 2;
histogram_index = mb.distance_context_map[context];
}
int context = (distance_it.type_ << 2) +
((cmd.copy_length_ > 4) ? 3 : cmd.copy_length_ - 2);
histogram_index = mb.distance_context_map[context];
EncodeCopyDistance(cmd, distance_codes[histogram_index],
storage_ix, storage);
}
@@ -733,45 +748,123 @@ void StoreMetaBlock(const MetaBlock& mb,
}
}
static const int kWindowBits = 22;
// To make decoding faster, we allow the decoder to write 16 bytes ahead in
// its ringbuffer, therefore the encoder has to decrease max distance by this
// amount.
static const int kDecoderRingBufferWriteAheadSlack = 16;
static const int kMaxBackwardDistance =
(1 << kWindowBits) - kDecoderRingBufferWriteAheadSlack;
static const int kMetaBlockSizeBits = 21;
static const int kRingBufferBits = 23;
static const int kRingBufferMask = (1 << kRingBufferBits) - 1;
BrotliCompressor::BrotliCompressor()
: hasher_(new Hasher),
dist_ringbuffer_idx_(0),
input_pos_(0),
ringbuffer_(kRingBufferBits, kMetaBlockSizeBits),
literal_cost_(1 << kRingBufferBits),
storage_ix_(0),
storage_(new uint8_t[2 << kMetaBlockSizeBits]) {
dist_ringbuffer_[0] = 4;
dist_ringbuffer_[1] = 11;
dist_ringbuffer_[2] = 15;
dist_ringbuffer_[3] = 16;
storage_[0] = 0;
}
BrotliCompressor::~BrotliCompressor() {
delete hasher_;
delete[] storage_;
}
void BrotliCompressor::WriteStreamHeader() {
// Don't encode input size.
WriteBits(3, 0, &storage_ix_, storage_);
// Encode window size.
WriteBits(1, 1, &storage_ix_, storage_);
WriteBits(3, kWindowBits - 17, &storage_ix_, storage_);
}
void BrotliCompressor::WriteMetaBlock(const size_t input_size,
const uint8_t* input_buffer,
size_t* encoded_size,
uint8_t* encoded_buffer) {
ringbuffer_.Write(input_buffer, input_size);
EstimateBitCostsForLiterals(input_pos_, input_size,
kRingBufferMask, ringbuffer_.start(),
&literal_cost_[0]);
std::vector<Command> commands;
CreateBackwardReferences(input_size, input_pos_,
ringbuffer_.start(),
&literal_cost_[0],
kRingBufferMask, kMaxBackwardDistance,
hasher_,
&commands);
ComputeDistanceShortCodes(&commands, dist_ringbuffer_,
&dist_ringbuffer_idx_);
EncodingParams params;
params.num_direct_distance_codes = 12;
params.distance_postfix_bits = 1;
params.literal_context_mode = CONTEXT_SIGNED;
MetaBlock mb;
BuildMetaBlock(params, commands, ringbuffer_.start(), input_pos_,
kRingBufferMask, &mb);
StoreMetaBlock(mb, ringbuffer_.start(), kRingBufferMask,
&input_pos_, &storage_ix_, storage_);
size_t output_size = storage_ix_ >> 3;
memcpy(encoded_buffer, storage_, output_size);
*encoded_size = output_size;
storage_ix_ -= output_size << 3;
storage_[storage_ix_ >> 3] = storage_[output_size];
}
void BrotliCompressor::FinishStream(
size_t* encoded_size, uint8_t* encoded_buffer) {
WriteBits(1, 1, &storage_ix_, storage_);
*encoded_size = (storage_ix_ + 7) >> 3;
memcpy(encoded_buffer, storage_, *encoded_size);
}
int BrotliCompressBuffer(size_t input_size,
const uint8_t* input_buffer,
size_t* encoded_size,
uint8_t* encoded_buffer) {
int storage_ix = 0;
uint8_t* storage = encoded_buffer;
WriteBitsPrepareStorage(storage_ix, storage);
EncodeSize(input_size, &storage_ix, storage);
if (input_size == 0) {
*encoded_size = (storage_ix + 7) >> 3;
encoded_buffer[0] = 1;
encoded_buffer[1] = 0;
*encoded_size = 2;
return 1;
}
int input_size_bits = Log2Ceiling(input_size);
std::vector<Command> all_commands;
CreateBackwardReferences(input_buffer, input_size, &all_commands);
ComputeDistanceShortCodes(&all_commands);
BrotliCompressor compressor;
compressor.WriteStreamHeader();
std::vector<std::vector<Command> > meta_block_commands;
SplitBlockByTotalLength(all_commands, input_size, 2 << 20,
&meta_block_commands);
const int max_block_size = 1 << kMetaBlockSizeBits;
size_t max_output_size = *encoded_size;
const uint8_t* input_end = input_buffer + input_size;
*encoded_size = 0;
size_t pos = 0;
for (int block_idx = 0; block_idx < meta_block_commands.size(); ++block_idx) {
const std::vector<Command>& commands = meta_block_commands[block_idx];
bool is_last_meta_block = (block_idx + 1 == meta_block_commands.size());
EncodingParams params;
params.num_direct_distance_codes = 12;
params.distance_postfix_bits = 1;
params.literal_context_mode = CONTEXT_SIGNED_MIXED_3BYTE;
params.distance_context_mode = 1;
MetaBlock mb;
BuildMetaBlock(params, commands, input_buffer, pos, &mb);
StoreMetaBlock(mb, input_buffer, input_size_bits, is_last_meta_block,
&pos, &storage_ix, storage);
while (input_buffer < input_end) {
int block_size = max_block_size;
if (block_size >= input_end - input_buffer) {
block_size = input_end - input_buffer;
}
size_t output_size = max_output_size;
compressor.WriteMetaBlock(block_size, input_buffer,
&output_size, &encoded_buffer[*encoded_size]);
input_buffer += block_size;
*encoded_size += output_size;
max_output_size -= output_size;
}
*encoded_size = (storage_ix + 7) >> 3;
size_t output_size = max_output_size;
compressor.FinishStream(&output_size, &encoded_buffer[*encoded_size]);
*encoded_size += output_size;
return 1;
}
+36
View File
@@ -20,9 +20,45 @@
#include <stddef.h>
#include <stdint.h>
#include <string>
#include <vector>
#include "./hash.h"
#include "./ringbuffer.h"
namespace brotli {
class BrotliCompressor {
public:
BrotliCompressor();
~BrotliCompressor();
// Writes the stream header into the internal output buffer.
void WriteStreamHeader();
// Encodes the data in input_buffer as a meta-block and writes it to
// encoded_buffer and sets *encoded_size to the number of bytes that was
// written.
void WriteMetaBlock(const size_t input_size,
const uint8_t* input_buffer,
size_t* encoded_size,
uint8_t* encoded_buffer);
// Writes a zero-length meta-block with end-of-input bit set to the
// internal output buffer and copies the output buffer to encoded_buffer and
// sets *encoded_size to the number of bytes written.
void FinishStream(size_t* encoded_size, uint8_t* encoded_buffer);
private:
Hasher* hasher_;
int dist_ringbuffer_[4];
size_t dist_ringbuffer_idx_;
size_t input_pos_;
RingBuffer ringbuffer_;
std::vector<float> literal_cost_;
int storage_ix_;
uint8_t* storage_;
};
// Compresses the data in input_buffer into encoded_buffer, and sets
// *encoded_size to the compressed length.
// Returns 0 if there was an error and 1 otherwise.
+7 -5
View File
@@ -43,6 +43,9 @@ HuffmanTree::HuffmanTree() {}
// Sort the root nodes, least popular first.
bool SortHuffmanTree(const HuffmanTree &v0, const HuffmanTree &v1) {
if (v0.total_count_ == v1.total_count_) {
return v0.index_right_or_value_ > v1.index_right_or_value_;
}
return v0.total_count_ < v1.total_count_;
}
@@ -276,7 +279,7 @@ int OptimizeHuffmanCountsForRle(int length, int* counts) {
}
// 3) Let's replace those population counts that lead to more rle codes.
stride = 0;
limit = counts[0];
limit = (counts[0] + counts[1] + counts[2]) / 3 + 1;
sum = 0;
for (i = 0; i < length + 1; ++i) {
if (i == length || good_for_rle[i] ||
@@ -301,11 +304,10 @@ int OptimizeHuffmanCountsForRle(int length, int* counts) {
}
stride = 0;
sum = 0;
if (i < length - 3) {
if (i < length - 2) {
// All interesting strides have a count of at least 4,
// at least when non-zeros.
limit = (counts[i] + counts[i + 1] +
counts[i + 2] + counts[i + 3] + 2) / 4;
limit = (counts[i] + counts[i + 1] + counts[i + 2]) / 3 + 1;
} else if (i < length) {
limit = counts[i];
} else {
@@ -329,7 +331,7 @@ void WriteHuffmanTree(const uint8_t* depth, const int length,
uint8_t* tree,
uint8_t* extra_bits_data,
int* huffman_tree_size) {
int previous_value = 0;
int previous_value = 8;
for (uint32_t i = 0; i < length;) {
const int value = depth[i];
int reps = 1;
+3 -3
View File
@@ -66,8 +66,8 @@ struct EntropyCode {
uint16_t bits_[kSize];
// How many non-zero depth.
int count_;
// First two symbols with non-zero depth.
int symbols_[2];
// First four symbols with non-zero depth.
int symbols_[4];
};
template<int kSize>
@@ -82,7 +82,7 @@ void BuildEntropyCode(const Histogram<kSize>& histogram,
if (histogram.total_count_ == 0) return;
for (int i = 0; i < kSize; ++i) {
if (histogram.data_[i] > 0) {
if (code->count_ < 2) code->symbols_[code->count_] = i;
if (code->count_ < 4) code->symbols_[code->count_] = i;
++code->count_;
}
}
+31 -31
View File
@@ -103,8 +103,7 @@ template <int kBucketBits, int kBlockBits>
class HashLongestMatch {
public:
HashLongestMatch()
: literal_cost_(NULL),
last_distance1_(4),
: last_distance1_(4),
last_distance2_(11),
last_distance3_(15),
last_distance4_(16),
@@ -115,10 +114,6 @@ class HashLongestMatch {
void Reset() {
std::fill(&num_[0], &num_[sizeof(num_) / sizeof(num_[0])], 0);
}
void SetLiteralCost(float *cost) {
literal_cost_ = cost;
}
double literal_cost(int i) const { return literal_cost_[i]; }
// Look at 3 bytes at data.
// Compute a hash from these, and store the value of ix at that position.
@@ -146,25 +141,27 @@ class HashLongestMatch {
// into best_distance_out.
// Write the score of the best match into best_score_out.
bool FindLongestMatch(const uint8_t * __restrict data,
const float * __restrict literal_cost,
const size_t ring_buffer_mask,
const uint32_t cur_ix,
uint32_t max_length,
const uint32_t max_backward,
size_t * __restrict best_len_out,
size_t * __restrict best_distance_out,
double * __restrict best_score_out) {
const double start_cost4 = literal_cost_ == NULL ? 20 :
literal_cost_[cur_ix] +
literal_cost_[cur_ix + 1] +
literal_cost_[cur_ix + 2] +
literal_cost_[cur_ix + 3];
const double start_cost3 = literal_cost_ == NULL ? 15 :
literal_cost_[cur_ix] +
literal_cost_[cur_ix + 1] +
literal_cost_[cur_ix + 2] + 0.3;
double start_cost2 = literal_cost_ == NULL ? 10 :
literal_cost_[cur_ix] +
literal_cost_[cur_ix + 1] + 1.2;
const size_t cur_ix_masked = cur_ix & ring_buffer_mask;
const double start_cost4 = literal_cost == NULL ? 20 :
literal_cost[cur_ix_masked] +
literal_cost[(cur_ix + 1) & ring_buffer_mask] +
literal_cost[(cur_ix + 2) & ring_buffer_mask] +
literal_cost[(cur_ix + 3) & ring_buffer_mask];
const double start_cost3 = literal_cost == NULL ? 15 :
literal_cost[cur_ix_masked] +
literal_cost[(cur_ix + 1) & ring_buffer_mask] +
literal_cost[(cur_ix + 2) & ring_buffer_mask] + 0.3;
double start_cost2 = literal_cost == NULL ? 10 :
literal_cost[cur_ix_masked] +
literal_cost[(cur_ix + 1) & ring_buffer_mask] + 1.2;
bool match_found = false;
// Don't accept a short copy from far away.
double best_score = 8.25;
@@ -177,7 +174,7 @@ class HashLongestMatch {
size_t best_ix = 1;
// Try last distance first.
for (int i = 0; i < 16; ++i) {
int prev_ix = cur_ix;
size_t prev_ix = cur_ix;
switch(i) {
case 0: prev_ix -= last_distance1_; break;
case 1: prev_ix -= last_distance2_; break;
@@ -205,11 +202,13 @@ class HashLongestMatch {
if (PREDICT_FALSE(backward > max_backward)) {
continue;
}
if (data[cur_ix + best_len] != data[prev_ix + best_len]) {
prev_ix &= ring_buffer_mask;
if (data[cur_ix_masked + best_len] != data[prev_ix + best_len]) {
continue;
}
const size_t len =
FindMatchLengthWithLimit(&data[prev_ix], &data[cur_ix], max_length);
FindMatchLengthWithLimit(&data[prev_ix], &data[cur_ix_masked],
max_length);
if (len >= 3 || (len == 2 && i < 2)) {
// Comparing for >= 2 does not change the semantics, but just saves for
// a few unnecessary binary logarithms in backward reference score,
@@ -234,7 +233,7 @@ class HashLongestMatch {
}
}
}
const uint32_t key = Hash3Bytes(&data[cur_ix], kBucketBits);
const uint32_t key = Hash3Bytes(&data[cur_ix_masked], kBucketBits);
const uint32_t * __restrict const bucket = &buckets_[key][0];
const int down = (num_[key] > kBlockSize) ? (num_[key] - kBlockSize) : 0;
int stop = int(cur_ix) - 64;
@@ -247,8 +246,9 @@ class HashLongestMatch {
if (PREDICT_FALSE(backward > max_backward)) {
break;
}
if (data[cur_ix] != data[prev_ix] ||
data[cur_ix + 1] != data[prev_ix + 1]) {
prev_ix &= ring_buffer_mask;
if (data[cur_ix_masked] != data[prev_ix] ||
data[cur_ix_masked + 1] != data[prev_ix + 1]) {
continue;
}
int len = 2;
@@ -269,11 +269,13 @@ class HashLongestMatch {
if (PREDICT_FALSE(backward > max_backward)) {
break;
}
if (data[cur_ix + best_len] != data[prev_ix + best_len]) {
prev_ix &= ring_buffer_mask;
if (data[cur_ix_masked + best_len] != data[prev_ix + best_len]) {
continue;
}
const size_t len =
FindMatchLengthWithLimit(&data[prev_ix], &data[cur_ix], max_length);
FindMatchLengthWithLimit(&data[prev_ix], &data[cur_ix_masked],
max_length);
if (len >= 3) {
// Comparing for >= 3 does not change the semantics, but just saves for
// a few unnecessary binary logarithms in backward reference score,
@@ -333,10 +335,6 @@ class HashLongestMatch {
// Buckets containing kBlockSize of backward references.
uint32_t buckets_[kBucketSize][kBlockSize];
// Model of how much the ith literal costs to encode using
// the entropy model.
float *literal_cost_;
int last_distance1_;
int last_distance2_;
int last_distance3_;
@@ -349,6 +347,8 @@ class HashLongestMatch {
double average_cost_;
};
typedef HashLongestMatch<13, 11> Hasher;
} // namespace brotli
#endif // BROTLI_ENC_HASH_H_
+36 -14
View File
@@ -31,10 +31,10 @@ void BuildHistograms(
const BlockSplit& literal_split,
const BlockSplit& insert_and_copy_split,
const BlockSplit& dist_split,
const uint8_t* input_buffer,
const uint8_t* ringbuffer,
size_t pos,
int context_mode,
int distance_context_mode,
size_t mask,
const std::vector<int>& context_modes,
std::vector<HistogramLiteral>* literal_histograms,
std::vector<HistogramCommand>* insert_and_copy_histograms,
std::vector<HistogramDistance>* copy_dist_histograms) {
@@ -48,25 +48,47 @@ void BuildHistograms(
cmd.command_prefix_);
for (int j = 0; j < cmd.insert_length_; ++j) {
literal_it.Next();
uint8_t prev_byte = pos > 0 ? input_buffer[pos - 1] : 0;
uint8_t prev_byte2 = pos > 1 ? input_buffer[pos - 2] : 0;
uint8_t prev_byte3 = pos > 2 ? input_buffer[pos - 3] : 0;
int context = (literal_it.type_ * NumContexts(context_mode) +
Context(prev_byte, prev_byte2, prev_byte3, context_mode));
(*literal_histograms)[context].Add(input_buffer[pos]);
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, context_modes[literal_it.type_]);
(*literal_histograms)[context].Add(ringbuffer[pos & mask]);
++pos;
}
pos += cmd.copy_length_;
if (cmd.copy_length_ > 0 && cmd.distance_prefix_ != 0xffff) {
dist_it.Next();
int context = dist_it.type_;
if (distance_context_mode > 0) {
context <<= 2;
context += (cmd.copy_length_ > 4) ? 3 : cmd.copy_length_ - 2;
}
int context = (dist_it.type_ << kDistanceContextBits) +
((cmd.copy_length_ > 4) ? 3 : cmd.copy_length_ - 2);
(*copy_dist_histograms)[context].Add(cmd.distance_prefix_);
}
}
}
void BuildLiteralHistogramsForBlockType(
const std::vector<Command>& cmds,
const BlockSplit& literal_split,
const uint8_t* ringbuffer,
size_t pos,
size_t mask,
int block_type,
int context_mode,
std::vector<HistogramLiteral>* histograms) {
BlockSplitIterator literal_it(literal_split);
for (int i = 0; i < cmds.size(); ++i) {
const Command &cmd = cmds[i];
for (int j = 0; j < cmd.insert_length_; ++j) {
literal_it.Next();
if (literal_it.type_ == block_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 = Context(prev_byte, prev_byte2, context_mode);
(*histograms)[context].Add(ringbuffer[pos & mask]);
}
++pos;
}
pos += cmd.copy_length_;
}
}
} // namespace brotli
+16 -3
View File
@@ -79,19 +79,32 @@ typedef Histogram<kNumCommandPrefixes> HistogramCommand;
typedef Histogram<kNumDistancePrefixes> HistogramDistance;
typedef Histogram<kNumBlockLenPrefixes> HistogramBlockLength;
static const int kLiteralContextBits = 6;
static const int kDistanceContextBits = 2;
void BuildHistograms(
const std::vector<Command>& cmds,
const BlockSplit& literal_split,
const BlockSplit& insert_and_copy_split,
const BlockSplit& dist_split,
const uint8_t* input_buffer,
const uint8_t* ringbuffer,
size_t pos,
int context_mode,
int distance_context_mode,
size_t mask,
const std::vector<int>& context_modes,
std::vector<HistogramLiteral>* literal_histograms,
std::vector<HistogramCommand>* insert_and_copy_histograms,
std::vector<HistogramDistance>* copy_dist_histograms);
void BuildLiteralHistogramsForBlockType(
const std::vector<Command>& cmds,
const BlockSplit& literal_split,
const uint8_t* ringbuffer,
size_t pos,
size_t mask,
int block_type,
int context_mode,
std::vector<HistogramLiteral>* histograms);
} // namespace brotli
#endif // BROTLI_ENC_HISTOGRAM_H_
+12 -10
View File
@@ -22,37 +22,39 @@
namespace brotli {
void EstimateBitCostsForLiterals(size_t len, const uint8_t *data, float *cost) {
void EstimateBitCostsForLiterals(size_t pos, size_t len, size_t mask,
const uint8_t *data, float *cost) {
int histogram[256] = { 0 };
int window_half = 2000;
int in_window = std::min(static_cast<size_t>(window_half), len);
// Bootstrap histogram.
for (int i = 0; i < in_window; ++i) {
++histogram[data[i]];
++histogram[data[(pos + i) & mask]];
}
// Compute bit costs with sliding window.
for (int i = 0; i < len; ++i) {
if (i - window_half >= 0) {
// Remove a byte in the past.
--histogram[data[i - window_half]];
--histogram[data[(pos + i - window_half) & mask]];
--in_window;
}
if (i + window_half < len) {
// Add a byte in the future.
++histogram[data[i + window_half]];
++histogram[data[(pos + i + window_half) & mask]];
++in_window;
}
int histo = histogram[data[i]];
int masked_pos = (pos + i) & mask;
int histo = histogram[data[masked_pos]];
if (histo == 0) {
histo = 1;
}
cost[i] = log2(static_cast<double>(in_window) / histo);
cost[i] += 0.03;
if (cost[i] < 1.0) {
cost[i] *= 0.5;
cost[i] += 0.5;
cost[masked_pos] = log2(static_cast<double>(in_window) / histo);
cost[masked_pos] += 0.03;
if (cost[masked_pos] < 1.0) {
cost[masked_pos] *= 0.5;
cost[masked_pos] += 0.5;
}
}
}
+5 -3
View File
@@ -22,9 +22,11 @@
namespace brotli {
// Input: length of data, and the bytes.
// Output: estimate of how many bits the literal will take entropy coded.
void EstimateBitCostsForLiterals(size_t len, const uint8_t *data, float *cost);
// Estimates how many bits the literals in the interval [pos, pos + len) in the
// ringbuffer (data, mask) will take entropy coded and writes these estimates
// to the ringbuffer (cost, mask).
void EstimateBitCostsForLiterals(size_t pos, size_t len, size_t mask,
const uint8_t *data, float *cost);
} // namespace brotli
+89
View File
@@ -0,0 +1,89 @@
// Copyright 2013 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.
//
// Sliding window over the input data.
#ifndef BROTLI_ENC_RINGBUFFER_H_
#define BROTLI_ENC_RINGBUFFER_H_
// A RingBuffer(window_bits, tail_bits) contains `1 << window_bits' bytes of
// data in a circular manner: writing a byte writes it to
// `position() % (1 << window_bits)'. For convenience, the RingBuffer array
// contains another copy of the first `1 << tail_bits' bytes:
// buffer_[i] == buffer_[i + (1 << window_bits)] if i < (1 << tail_bits).
class RingBuffer {
public:
RingBuffer(int window_bits, int tail_bits)
: window_bits_(window_bits), tail_bits_(tail_bits), pos_(0) {
static const int kSlackForThreeByteHashingEverywhere = 2;
const int buflen = (1 << window_bits_) + (1 << tail_bits_);
buffer_ = new uint8_t[buflen + kSlackForThreeByteHashingEverywhere];
for (int i = 0; i < kSlackForThreeByteHashingEverywhere; ++i) {
buffer_[buflen + i] = 0;
}
}
~RingBuffer() {
delete [] buffer_;
}
// Push bytes into the ring buffer.
void Write(const uint8_t *bytes, size_t n) {
const size_t masked_pos = pos_ & ((1 << window_bits_) - 1);
// The length of the writes is limited so that we do not need to worry
// about a write
WriteTail(bytes, n);
if (masked_pos + n <= (1 << window_bits_)) {
// A single write fits.
memcpy(&buffer_[masked_pos], bytes, n);
} else {
// Split into two writes.
// Copy into the end of the buffer, including the tail buffer.
memcpy(&buffer_[masked_pos], bytes,
std::min(n,
((1 << window_bits_) + (1 << tail_bits_)) - masked_pos));
// Copy into the begining of the buffer
memcpy(&buffer_[0], bytes + ((1 << window_bits_) - masked_pos),
n - ((1 << window_bits_) - masked_pos));
}
pos_ += n;
}
// Logical cursor position in the ring buffer.
size_t position() const { return pos_; }
uint8_t *start() { return &buffer_[0]; }
const uint8_t *start() const { return &buffer_[0]; }
private:
void WriteTail(const uint8_t *bytes, size_t n) {
const size_t masked_pos = pos_ & ((1 << window_bits_) - 1);
if (masked_pos < (1 << tail_bits_)) {
// Just fill the tail buffer with the beginning data.
const size_t p = (1 << window_bits_) + masked_pos;
memcpy(&buffer_[p], bytes, std::min(n, (1 << tail_bits_) - masked_pos));
}
}
// Size of the ringbuffer is (1 << window_bits) + (1 << tail_bits).
const int window_bits_;
const int tail_bits_;
// Position to write in the ring buffer.
size_t pos_;
// The actual ring buffer containing the data and the copy of the beginning
// as a tail.
uint8_t *buffer_;
};
#endif // BROTLI_ENC_RINGBUFFER_H_