最近终于有空研究研究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
…
{
inthash_start[256];
inthash_next[4096];
inthash_prev[4096];
chardict[4096];
}
lz77_dict,
*
p_lz77_dict;
其中,hash_start是256个哈希码的首码索引。hash_next是每个哈希码的下一个编码,hash_prev是每个哈希码的前一个编码。均以-1表示无效。
字典初始化函数如下:
void
init_dict(p_lz77_dict pdic)
…
{
inti;
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)
…
{
chara, b, c;
unsignedcharhash;
intold_pos=pos;
ints, 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)
…
{
chara, b, c;
unsignedcharhash;
intold_pos=pos;
intlast;
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;
intdict_start=4096–16;
intleft=size;
unsignedcharbits=0;
intbitcount=8;
char*bits_pos=dest;
if(size==0)…{
free(dest);
returnNULL;
} init_dict(&dict);
dest++;
while(left>0)…{
intmax_len=0;
inttable_index=0;
inti;
if(left>=3)…{
unsignedcharhash=HASH(buf[0], buf[1], buf[2]);
intoffset=dict.hash_start[hash];
if(offset>=0)…{
intlen=0;
while(offset>=0)…{
intpos=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)…{
inti;
intpos=dict_start–2;
intold_pos;
*(unsignedshort*)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
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…{
intpos=dict_start–2;
inti;
/**//*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)…{
//fullbitcount=8;
*bits_pos=bits;
bits_pos=dest++;
bits=0;
} bits>>=1;
}*bits_pos=bits>>bitcount;
*dest_size=dest–org_dest;
returnorg_dest;
}