[原创]用哈希表优化的lz77压缩算法的实现

 

最近终于有空研究研究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, 04096);
    
for (i = 0; i < 256++i) pdic->hash_start[i] = -1;
    pdic
->hash_start[HASH(000)] = 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;
}

 

你可能感兴趣的:([原创]用哈希表优化的lz77压缩算法的实现)