最近终于有空研究研究E*F的K*KYU2。
和预料到的一样仍然是广泛使用LZ77,而且是毫不改变地使用LZ77……但是,时代进步了, 图片文件都是真彩色的了,大小变大了3倍,仍然使用LZ77的代价就是速度……大家都知道LZ77的特点就是解压超快,压缩巨慢(不然就不会有LZW这种 不伦不类的算法出来了……)
在png的相关网站上查找了一下优化方案,自己写了一下优化代码,虽然目前速度仍然不能很让人满意(在双核的机器上压 缩一个160多兆的文件用了60多秒),但是勉强可以用于实际应用了。贴出来和大家分享。如果要进一步优化,估计要玩一些trick,或是借助于指令级别 的。
以下讨论是基于4096字节定长窗口的字典的LZ77,字节组织是每bit表示查表或是原数据;查表是2字节,前16位表示索引,后16位表示长度,因此最长的序列只能有15字节。优化的思路是哈希表。
由 环境可以看出,低于3个字节的码序列是不需要查表保存的,因此码的最短长度是3个字节。使用一个哈希算法将之转化成1个字节的哈希码。这种哈希码共有 256个。当在查表前,首先要计算出当前字节及后面两个字节组成的序列的哈希码,然后在哈希码表中查得第一条匹配记录。当然,即使哈希码匹配,3字节并不 一定完全一样,这之后还是需要按字节比较。但这可以提前淘汰大部分的数据。匹配后,再查下一条记录。
优化的负担是额外增加了一个哈希码头指针表和 一个链表。每一个字节在字典中的更新都会造成它本身及前面两个字节处共3个字节的哈希码变化,即表项删除和增加。为了使这种删除和增加更有效率,做成了双 向链表的形式(实际测试下来比单向节省约一半时间)。当然这里的链表是以数组形式实现的,不需要动态分配空间,否则所带来的负担将是不可承受的。字典定义 如下:
typedef
struct
_tagLz77Dict
{
int hash_start[256];
int hash_next[4096];
int hash_prev[4096];
char dict[4096];
}
lz77_dict,
*
p_lz77_dict;
其中,hash_start是256个哈希码的首码索引。hash_next是每个哈希码的下一个编码,hash_prev是每个哈希码的前一个编码。均以-1表示无效。
字典初始化函数如下:
void
init_dict(p_lz77_dict pdic)
{
int i;
memset(pdic->dict, 0, 4096);
for (i = 0; i < 256; ++i) pdic->hash_start[i] = –1;
pdic->hash_start[HASH(0, 0, 0)] = 4096–16;
for (i = 0; i < 4096; ++i) {
if (i < 4096–16) {
pdic->hash_next[i] = –1;
pdic->hash_prev[i] = –1;
} else {
pdic->hash_next[i] = (i+1);
pdic->hash_prev[i] = (i–1);
}
}
pdic->hash_prev[0] = –1;
pdic->hash_next[4095] = –1;
}
初始值仅后面16个码可用。
删除和增加哈希码函数:
void
remove_dict_pos(p_lz77_dict pdic,
int
pos)
{
char a, b, c;
unsigned char hash;
int old_pos = pos;
int s, last;
a = pdic->dict[pos++];
pos &= 0xFFF;
b = pdic->dict[pos++];
pos &= 0xFFF;
c = pdic->dict[pos++];
pos &= 0xFFF;
hash = HASH(a, b, c);
last = pdic->hash_start[hash];
if (last == old_pos) {
// head?
last = pdic->hash_next[old_pos];
pdic->hash_start[hash] = last;
if (last >= 0) {
pdic->hash_prev[last] = –1;
}
} else {
last = pdic->hash_prev[old_pos];
s = pdic->hash_next[old_pos];
if (last >= 0) {
pdic->hash_next[last] = s;
}
if (s >= 0) {
pdic->hash_prev[s] = last;
}
pdic->hash_prev[old_pos] = –1;
}
}
//
add a position’s hash rec
void
add_dict_pos(p_lz77_dict pdic,
int
pos)
{
char a, b, c;
unsigned char hash;
int old_pos = pos;
int last;
a = pdic->dict[pos++];
pos &= 0xFFF;
b = pdic->dict[pos++];
pos &= 0xFFF;
c = pdic->dict[pos++];
pos &= 0xFFF;
hash = HASH(a, b, c);
last = pdic->hash_start[hash];
pdic->hash_start[hash] = old_pos;
if (last >= 0) pdic->hash_prev[last] = old_pos;
pdic->hash_next[old_pos] = last;
}
哈希算法随便选了一个(注意尽量不要全是3个字节异或,这样会使效率受影响,因为在实际应用中3个字节中有两个以上相同的比较多)
#define HASH(a, b, c) (((unsigned char)a) + ((unsigned char)b) ^ ((unsigned char)c))
最后是解码函数主体:
//
dest_buf should allocate size+size/8 bytes at least
//
optimized algorithm
char
*
fast_lz77_encode(
const
char
*
buf,
int
size,
int
*
dest_size)
{
nsg_auto_detect(__FUNCTION__);
char* dest = (char*)malloc(size + size/8 + 1);
char* org_dest = dest;
lz77_dict dict;
int dict_start = 4096 – 16;
int left = size;
unsigned char bits = 0;
int bitcount = 8;
char* bits_pos = dest;
if (size == 0) {
free(dest);
return NULL;
}
init_dict(&dict);
dest++;
while(left > 0) {
int max_len = 0;
int table_index = 0;
int i;
if (left >= 3) {
unsigned char hash = HASH(buf[0], buf[1], buf[2]);
int offset = dict.hash_start[hash];
if (offset >= 0) {
int len = 0;
while (offset >= 0) {
int pos = offset;
len = 0;
while (len < 15) {
if (dict.dict[pos++] != buf[len]) break;
pos &= 0xFFF;
++len;
}
if (len > max_len) {
table_index = (offset – len) & 0xFFF;
max_len = len;
}
offset = dict.hash_next[offset];
}
}
}
if (max_len >= 3) {
int i;
int pos = dict_start – 2;
int old_pos;
*(unsigned short*)dest = (table_index << 4) | max_len;
if (pos < 0) pos += 4096;
old_pos = pos;
// remove previous 2, next 2 dict
for (i = max_len + 2; i >= 0; —i) {
remove_dict_pos(&dict, pos++);
pos &= 0xFFF;
}
// update dict
for (i = 0; i < max_len; ++i ) {
dict.dict[dict_start++] = buf[i];
dict_start &= 0xFFF;
}
pos = old_pos;
for (i = max_len + 2; i >= 0; —i) {
add_dict_pos(&dict, pos++);
pos &= 0xFFF;
}
dest += 2;
buf += max_len;
left -= max_len;
} else {
int pos = dict_start – 2;
int i;
/* bits |= 0; */ /* not necessary */
if (pos < 0) pos += 4096;
for (i = 0; i < 3; ++i ) {
remove_dict_pos(&dict, pos++);
pos &= 0xFFF;
}
dict.dict[dict_start++] = *buf;
dict_start &= 0xFFF;
pos -= 3;
if (pos < 0) pos += 4096;
for (i = 0; i < 3; ++i ) {
add_dict_pos(&dict, pos++);
pos &= 0xFFF;
}
*dest++ = *buf++;
—left;
}
if (—bitcount == 0) {
// full
bitcount = 8;
*bits_pos = bits;
bits_pos = dest++;
bits = 0;
}
bits >>= 1;
}
*bits_pos = bits >> bitcount;
*dest_size = dest – org_dest;
return org_dest;
}