577 lines
13 KiB
C
577 lines
13 KiB
C
/*
|
|
* encstats.c
|
|
*
|
|
* Routines for calculating statistics on a block of data which
|
|
* has been compressed, but not yet output.
|
|
*
|
|
* These routines are used to determine which encoding method to use
|
|
* to output the block.
|
|
*/
|
|
|
|
#include "encoder.h"
|
|
|
|
|
|
|
|
static void tally_aligned_bits(t_encoder_context *context, ulong dist_to_end_at)
|
|
{
|
|
ulong *dist_ptr;
|
|
ulong i;
|
|
ulong match_pos;
|
|
|
|
/*
|
|
* Tally the lower 3 bits
|
|
*/
|
|
dist_ptr = context->enc_DistData;
|
|
|
|
for (i = dist_to_end_at; i > 0; i--)
|
|
{
|
|
match_pos = *dist_ptr++;
|
|
|
|
/*
|
|
* Only for matches which have >= 3 extra bits
|
|
*/
|
|
if (match_pos >= MPSLOT3_CUTOFF)
|
|
context->enc_aligned_tree_freq[match_pos & 7]++;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Determine whether it is advantageous to use aligned block
|
|
* encoding on the block.
|
|
*/
|
|
lzx_block_type get_aligned_stats(t_encoder_context *context, ulong dist_to_end_at)
|
|
{
|
|
|
|
byte i;
|
|
ulong total_L3 = 0;
|
|
ulong largest_L3 = 0;
|
|
|
|
memset(
|
|
context->enc_aligned_tree_freq,
|
|
0,
|
|
ALIGNED_NUM_ELEMENTS * sizeof(context->enc_aligned_tree_freq[0])
|
|
);
|
|
|
|
tally_aligned_bits(context, dist_to_end_at);
|
|
|
|
for (i = 0; i < ALIGNED_NUM_ELEMENTS; i++)
|
|
{
|
|
if (context->enc_aligned_tree_freq[i] > largest_L3)
|
|
largest_L3 = context->enc_aligned_tree_freq[i];
|
|
|
|
total_L3 += context->enc_aligned_tree_freq[i];
|
|
}
|
|
|
|
/*
|
|
* Do aligned offsets if the largest frequency accounts for 20%
|
|
* or more (as opposed to 12.5% for non-aligned offset blocks).
|
|
*
|
|
* Not worthwhile to do aligned offsets if we have < 100 matches
|
|
*/
|
|
if ((largest_L3 > total_L3/5) && dist_to_end_at >= 100)
|
|
return BLOCKTYPE_ALIGNED;
|
|
else
|
|
return BLOCKTYPE_VERBATIM;
|
|
}
|
|
|
|
|
|
/*
|
|
* Calculates the frequency of each literal, and returns the total
|
|
* number of uncompressed bytes compressed in the block.
|
|
*/
|
|
static ulong tally_frequency(
|
|
t_encoder_context *context,
|
|
ulong literal_to_start_at,
|
|
ulong distance_to_start_at,
|
|
ulong literal_to_end_at
|
|
)
|
|
{
|
|
|
|
ulong i;
|
|
ulong d;
|
|
ulong compressed_bytes = 0;
|
|
|
|
d = distance_to_start_at;
|
|
|
|
for (i = literal_to_start_at; i < literal_to_end_at; i++)
|
|
{
|
|
if (!IsMatch(i))
|
|
{
|
|
/* Uncompressed symbol */
|
|
context->enc_main_tree_freq[context->enc_LitData[i]]++;
|
|
compressed_bytes++;
|
|
}
|
|
else
|
|
{
|
|
/* Match */
|
|
if (context->enc_LitData[i] < NUM_PRIMARY_LENGTHS)
|
|
{
|
|
context->enc_main_tree_freq[ NUM_CHARS + (MP_SLOT(context->enc_DistData[d])<<NL_SHIFT) + context->enc_LitData[i]] ++;
|
|
}
|
|
else
|
|
{
|
|
context->enc_main_tree_freq[ (NUM_CHARS + NUM_PRIMARY_LENGTHS) + (MP_SLOT(context->enc_DistData[d])<<NL_SHIFT)] ++;
|
|
context->enc_secondary_tree_freq[context->enc_LitData[i] - NUM_PRIMARY_LENGTHS] ++;
|
|
}
|
|
|
|
compressed_bytes += context->enc_LitData[i]+MIN_MATCH;
|
|
d++;
|
|
}
|
|
}
|
|
|
|
return compressed_bytes;
|
|
}
|
|
|
|
|
|
/*
|
|
* Get statistics
|
|
*/
|
|
ulong get_block_stats(
|
|
t_encoder_context *context,
|
|
ulong literal_to_start_at,
|
|
ulong distance_to_start_at,
|
|
ulong literal_to_end_at
|
|
)
|
|
{
|
|
|
|
memset(
|
|
context->enc_main_tree_freq,
|
|
0,
|
|
MAIN_TREE_ELEMENTS * sizeof(context->enc_main_tree_freq[0])
|
|
);
|
|
|
|
memset(
|
|
context->enc_secondary_tree_freq,
|
|
0,
|
|
NUM_SECONDARY_LENGTHS * sizeof(context->enc_secondary_tree_freq[0])
|
|
);
|
|
|
|
return tally_frequency(
|
|
context,
|
|
literal_to_start_at,
|
|
distance_to_start_at,
|
|
literal_to_end_at
|
|
);
|
|
}
|
|
|
|
|
|
/*
|
|
* Update cumulative statistics
|
|
*/
|
|
ulong update_cumulative_block_stats(
|
|
t_encoder_context *context,
|
|
ulong literal_to_start_at,
|
|
ulong distance_to_start_at,
|
|
ulong literal_to_end_at
|
|
)
|
|
{
|
|
|
|
return tally_frequency(
|
|
context,
|
|
literal_to_start_at,
|
|
distance_to_start_at,
|
|
literal_to_end_at
|
|
);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Used in block splitting
|
|
*
|
|
* This routine calculates the "difference in composition" between
|
|
* two different sections of compressed data.
|
|
*
|
|
* Resolution must be evenly divisible by STEP_SIZE, and must be
|
|
* a power of 2.
|
|
*/
|
|
#define RESOLUTION 1024
|
|
|
|
/*
|
|
* Threshold for determining if two blocks are different
|
|
*
|
|
* If enough consecutive blocks are this different, the block
|
|
* splitter will start investigating, narrowing down the
|
|
* area where the change occurs.
|
|
*
|
|
* It will then look for two areas which are
|
|
* EARLY_BREAK_THRESHOLD (or more) different.
|
|
*
|
|
* If THRESHOLD is too small, it will force examination
|
|
* of a lot of blocks, slowing down the compressor.
|
|
*
|
|
* The EARLY_BREAK_THRESHOLD is the more important value.
|
|
*/
|
|
#define THRESHOLD 1400
|
|
|
|
/*
|
|
* Threshold for determining if two blocks are REALLY different
|
|
*/
|
|
#define EARLY_BREAK_THRESHOLD 1700
|
|
|
|
/*
|
|
* Must be >= 8 because ItemType[] array is in bits
|
|
*
|
|
* Must be a power of 2.
|
|
*
|
|
* This is the step size used to narrow down the exact
|
|
* best point to split the block.
|
|
*/
|
|
#define STEP_SIZE 64
|
|
|
|
/*
|
|
* Minimum # literals required to perform block
|
|
* splitting at all.
|
|
*/
|
|
#define MIN_LITERALS_REQUIRED 6144
|
|
|
|
/*
|
|
* Minimum # literals we will allow to be its own block.
|
|
*
|
|
* We don't want to create blocks with too small numbers
|
|
* of literals, otherwise the static tree output will
|
|
* take up too much space.
|
|
*/
|
|
#define MIN_LITERALS_IN_BLOCK 4096
|
|
|
|
|
|
static const long square_table[17] =
|
|
{
|
|
0,1,4,9,16,25,36,49,64,81,100,121,144,169,196,225,256
|
|
};
|
|
|
|
|
|
/*
|
|
* log2(x) = x < 256 ? log2_table[x] : 8 + log2_table[(x >> 8)]
|
|
*
|
|
* log2(0) = 0
|
|
* log2(1) = 1
|
|
* log2(2) = 2
|
|
* log2(3) = 2
|
|
* log2(4) = 3
|
|
* log2(255) = 8
|
|
* log2(256) = 9
|
|
* log2(511) = 9
|
|
* log2(512) = 10
|
|
*
|
|
* It's not a real log2; it's off by one because we have
|
|
* log2(0) = 0.
|
|
*/
|
|
static const byte log2_table[256] =
|
|
{
|
|
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,
|
|
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
|
|
};
|
|
|
|
|
|
/*
|
|
* Return the difference between two sets of matches/distances
|
|
*/
|
|
static ulong return_difference(
|
|
t_encoder_context *context,
|
|
ulong item_start1,
|
|
ulong item_start2,
|
|
ulong dist_at_1,
|
|
ulong dist_at_2,
|
|
ulong size
|
|
)
|
|
{
|
|
|
|
ushort freq1[800];
|
|
ushort freq2[800];
|
|
ulong i;
|
|
ulong cum_diff;
|
|
int element;
|
|
|
|
/*
|
|
* Error! Too many main tree elements
|
|
*/
|
|
if (MAIN_TREE_ELEMENTS >= (sizeof(freq1)/sizeof(freq1[0])))
|
|
return 0;
|
|
|
|
memset(freq1, 0, sizeof(freq1[0])*MAIN_TREE_ELEMENTS);
|
|
memset(freq2, 0, sizeof(freq2[0])*MAIN_TREE_ELEMENTS);
|
|
|
|
for (i = 0; i < size; i++)
|
|
{
|
|
if (!IsMatch(item_start1))
|
|
{
|
|
element = context->enc_LitData[item_start1];
|
|
}
|
|
else
|
|
{
|
|
if (context->enc_LitData[item_start1] < NUM_PRIMARY_LENGTHS)
|
|
element = NUM_CHARS + (MP_SLOT(context->enc_DistData[dist_at_1])<<NL_SHIFT) + context->enc_LitData[item_start1];
|
|
else
|
|
element = (NUM_CHARS + NUM_PRIMARY_LENGTHS) + (MP_SLOT(context->enc_DistData[dist_at_1]) << NL_SHIFT);
|
|
|
|
dist_at_1++;
|
|
}
|
|
|
|
item_start1++;
|
|
freq1[element]++;
|
|
|
|
if (!IsMatch(item_start2))
|
|
{
|
|
element = context->enc_LitData[item_start2];
|
|
}
|
|
else
|
|
{
|
|
if (context->enc_LitData[item_start2] < NUM_PRIMARY_LENGTHS)
|
|
element = NUM_CHARS + (MP_SLOT(context->enc_DistData[dist_at_2])<<NL_SHIFT) + context->enc_LitData[item_start2];
|
|
else
|
|
element = (NUM_CHARS + NUM_PRIMARY_LENGTHS) + (MP_SLOT(context->enc_DistData[dist_at_2]) << NL_SHIFT);
|
|
|
|
dist_at_2++;
|
|
}
|
|
|
|
item_start2++;
|
|
freq2[element]++;
|
|
}
|
|
|
|
cum_diff = 0;
|
|
|
|
for (i = 0; i < (ulong) MAIN_TREE_ELEMENTS; i++)
|
|
{
|
|
ulong log2a, log2b, diff;
|
|
|
|
#define log2(x) ((x) < 256 ? log2_table[(x)] : 8+log2_table[(x) >> 8])
|
|
|
|
log2a = (ulong) log2(freq1[i]);
|
|
log2b = (ulong) log2(freq2[i]);
|
|
|
|
/* diff = (log2a*log2a) - (log2b*log2b); */
|
|
diff = square_table[log2a] - square_table[log2b];
|
|
|
|
cum_diff += abs(diff);
|
|
}
|
|
|
|
return cum_diff;
|
|
}
|
|
|
|
|
|
/*
|
|
* Calculates where and if a block of compressed data should be split.
|
|
*
|
|
* For example, if we have just compressed text data, audio data, and
|
|
* more text data, then the composition of matches and unmatched
|
|
* symbols will be different between the text data and audio data.
|
|
* Therefore we force an end of block whenever the compressed data
|
|
* looks like it's changing in composition.
|
|
*
|
|
* This routine currently cannot tell the difference between blocks
|
|
* which should use aligned offsets, and blocks which should not.
|
|
* However, there is little to be gained from looking for this change,
|
|
* since it the match finder doesn't make an effort to look for
|
|
* aligned offsets either.
|
|
*
|
|
* Returns whether we split the block or not.
|
|
*/
|
|
bool split_block(
|
|
t_encoder_context *context,
|
|
ulong literal_to_start_at,
|
|
ulong literal_to_end_at,
|
|
ulong distance_to_end_at, /* corresponds to # distances at literal_to_end_at */
|
|
ulong *split_at_literal,
|
|
ulong *split_at_distance /* optional parameter (may be NULL) */
|
|
)
|
|
{
|
|
|
|
ulong i, j, d;
|
|
int nd;
|
|
|
|
/*
|
|
* num_dist_at_item[n] equals the cumulative number of matches
|
|
* at literal "n / STEP_SIZE".
|
|
*/
|
|
ushort num_dist_at_item[(MAX_LITERAL_ITEMS/STEP_SIZE)+8]; /* +8 is slop */
|
|
|
|
/*
|
|
* default return
|
|
*/
|
|
*split_at_literal = literal_to_end_at;
|
|
|
|
if (split_at_distance)
|
|
*split_at_distance = distance_to_end_at;
|
|
|
|
/* Not worth doing if we don't have many literals */
|
|
if (literal_to_end_at - literal_to_start_at < MIN_LITERALS_REQUIRED)
|
|
return false;
|
|
|
|
/* Not allowed to split blocks any more, so we don't overflow MAX_GROWTH? */
|
|
if (context->enc_num_block_splits >= MAX_BLOCK_SPLITS)
|
|
return false;
|
|
|
|
/*
|
|
* Keep track of the number of distances (matches) we've had,
|
|
* at each step of STEP_SIZE literals.
|
|
*
|
|
* Look at 8 items at a time, and ignore the last
|
|
* 0..7 items if they exist.
|
|
*/
|
|
nd = 0;
|
|
d = 0;
|
|
|
|
for (i = 0; i < (literal_to_end_at >> 3); i++)
|
|
{
|
|
/*
|
|
* if (i % (STEP_SIZE >> 3)) == 0
|
|
*/
|
|
if ((i & ((STEP_SIZE >> 3)-1)) == 0)
|
|
num_dist_at_item[nd++] = (ushort) d;
|
|
|
|
d += context->enc_ones[ context->enc_ItemType[i] ];
|
|
}
|
|
|
|
/*
|
|
* Must be a multiple of STEP_SIZE
|
|
*/
|
|
literal_to_start_at = (literal_to_start_at + (STEP_SIZE-1)) & (~(STEP_SIZE-1));
|
|
|
|
/*
|
|
* See where the change in composition occurs
|
|
*/
|
|
for ( i = literal_to_start_at + 2*RESOLUTION;
|
|
i < literal_to_end_at - 4*RESOLUTION;
|
|
i += RESOLUTION)
|
|
{
|
|
/*
|
|
* If there appears to be a significant variance in composition
|
|
* between
|
|
* ___________
|
|
* / \
|
|
* A B i X Y Z
|
|
* \ \___/ /
|
|
* \_______________/
|
|
*/
|
|
if (
|
|
return_difference(
|
|
context,
|
|
i,
|
|
i+1*RESOLUTION,
|
|
(ulong) num_dist_at_item[i/STEP_SIZE],
|
|
(ulong) num_dist_at_item[(i+1*RESOLUTION)/STEP_SIZE],
|
|
RESOLUTION) > THRESHOLD
|
|
&&
|
|
|
|
return_difference(
|
|
context,
|
|
i-RESOLUTION,
|
|
i+2*RESOLUTION,
|
|
(ulong) num_dist_at_item[(i-RESOLUTION)/STEP_SIZE],
|
|
(ulong) num_dist_at_item[(i+2*RESOLUTION)/STEP_SIZE],
|
|
RESOLUTION) > THRESHOLD
|
|
|
|
&&
|
|
|
|
return_difference(
|
|
context,
|
|
i-2*RESOLUTION,
|
|
i+3*RESOLUTION,
|
|
(ulong) num_dist_at_item[(i-2*RESOLUTION)/STEP_SIZE],
|
|
(ulong) num_dist_at_item[(i+3*RESOLUTION)/STEP_SIZE],
|
|
RESOLUTION) > THRESHOLD
|
|
)
|
|
{
|
|
ulong max_diff = 0;
|
|
ulong literal_split;
|
|
|
|
/*
|
|
* Narrow down the best place to split block
|
|
*
|
|
* This really could be done much better; we could end up
|
|
* doing a lot of stepping;
|
|
*
|
|
* basically ((5/2 - 1/2) * RESOLUTION) / STEP_SIZE
|
|
*
|
|
* which is (2 * RESOLUTION) / STEP_SIZE,
|
|
* which with RESOLUTION = 1024 and STEP_SIZE = 32,
|
|
* equals 2048/32 = 64 steps.
|
|
*/
|
|
for (j = i+RESOLUTION/2; j<i+(5*RESOLUTION)/2; j += STEP_SIZE)
|
|
{
|
|
ulong diff;
|
|
|
|
diff = return_difference(
|
|
context,
|
|
j - RESOLUTION,
|
|
j,
|
|
(ulong) num_dist_at_item[(j-RESOLUTION)/STEP_SIZE],
|
|
(ulong) num_dist_at_item[j/STEP_SIZE],
|
|
RESOLUTION
|
|
);
|
|
|
|
/* Get largest difference */
|
|
if (diff > max_diff)
|
|
{
|
|
/*
|
|
* j should not be too small, otherwise we'll be outputting
|
|
* a very small block
|
|
*/
|
|
max_diff = diff;
|
|
literal_split = j;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* There could be multiple changes in the data in our literals,
|
|
* so if we find something really weird, make sure we break the
|
|
* block now, and not on some later change.
|
|
*/
|
|
if (max_diff >= EARLY_BREAK_THRESHOLD &&
|
|
(literal_split-literal_to_start_at) >= MIN_LITERALS_IN_BLOCK)
|
|
{
|
|
context->enc_num_block_splits++;
|
|
|
|
*split_at_literal = literal_split;
|
|
|
|
/*
|
|
* Return the associated # distances, if required.
|
|
* Since we split on a literal which is % STEP_SIZE, we
|
|
* can read the # distances right off
|
|
*/
|
|
if (split_at_distance)
|
|
*split_at_distance = num_dist_at_item[literal_split/STEP_SIZE];
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* No good place found to split
|
|
*/
|
|
return false;
|
|
}
|