速度之王 — LZ4压缩算法(三)
LZ4 (Extremely Fast Compression algorithm)
项目:http://code.google.com/p/lz4/
作者:Yann Collet
本文作者:zhangskd @ csdn blog
实现
(3) 流操作
typedef struct { U32 hashTable[HASHNBCELLS4]; /* 哈希表 */ const BYTE *bufferStart; /* 类似于前向缓存 */ const BYTE *base; /* 哈希表中采用的基准地址srcBase */ const BYTE *nextBlock; /* 下一个块的地址 */ } LZ4_Data_Structure;
FORCE_INLINE void LZ4_init(LZ4_Data_Structure *lz4ds, const BYTE *base) { MEM_INIT(lz4ds->hashTable, 0, sizeof(lz4ds->hashTable)); lz4ds->bufferStart = base; lz4ds->base = base; lz4ds->nextBlock = base; }
创建和初始化LZ4_Data_Structure实例。
void *LZ4_create(const char *inputBuffer) { void *lz4ds = ALLOCATOR(1, sizeof(LZ4_Data_Structure)); LZ4_init((LZ4_Data_Structure *)lz4ds, (const BYTE *)inputBuffer); return lz4ds; }
释放LZ4_Data_Structure实例。
int LZ4_free(void *LZ4_Data) { FREEMEM(LZ4_Data); return 0; }
当输入缓存不够的时候,进行调整。
char *LZ4_slideInputBuffer(void *LZ4_Data) { LZ4_Data_Structure *lz4ds = (LZ4_Data_Structure *) LZ4_Data; size_t delta = lz4ds->nextBlock - (lz4ds->bufferStart + 64KB); /* 调整后地址的偏移 */ if ((lz4ds->base - delta > lz4ds->base) || /* underflow control */ (size_t) (lz4ds->nextBlock - (lz4ds->base) > 0xE0000000)) /* close to 32-bit limit */ { size_t deltaLimit = (lz4ds->nextBlock - 64KB) - lz4ds->base; int nH; for (nH = 0; nH < HASHNBCELLS4; nH++) { if ((size_t) (lz4ds->hashTable[nH]) < deltaLimit) lz4ds->hashTable[nH] = 0; else lz4ds->hashTable[nH] -= (U32) deltaLimit; } memcpy((void *)(lz4ds->bufferStart), (const void *)(lz4ds->nextBlock - 64KB), 64KB); lz4ds->base = lz4ds->bufferStart; lz4ds->nextBlock = lz4ds->base + 64KB; } else { /* 把下个块之前的64KB数据拷贝到buffer的头部 */ memcpy((void *)(lz4ds->bufferStart), (const void *)(lz4ds->nextBlock - 64KB), 64KB); lz4ds->nextBlock -= delta; /* 更新下个块的地址 */ /* 哈希表中的value为偏移值offset。 * pos = base + offset,现在offset -= delta,但是我们又不想去更新offset (更新哈希表)。 * 可以让base -= delta,这样可以不改变offset而取得正确的pos。 * pos是真实的地址。 */ lz4ds->base -= delta; } return (char *) (lz4ds->nextBlock); }
(4) 解压
LZ4_decompress_generic()是通用的解压算法,只要符合压缩格式就可以解压,无需考虑匹配算法。
typedef enum { endOnOutputSize = 0, endOnInputSize = 1 } endCondition_directive; typedef enum { full = 0, partial = 1 } earlyEnd_directive; If endOnInput == endOnInputSize, outputSize is the max size of Output Buffer. targetOutputSize only used if partialDecoding == partial.
FORCE_INLINE int LZ4_decompress_generic( const char *source, char *dest, int inputSize, int outputSize, int endOnInput, int prefix64k, int partialDecoding, int targetOutputSize) { /* Local variables */ const BYTE *restrict ip = (const BYTE *) source; const BYTE *ref; const BYTE *const iend = ip + inputSize; BYTE *op = (BYTE *) dest; BYTE *const oend = op + outputSize; BYTE *cpy; BYTE *oexit = op + targetOutputSize; /* static reduces speed for LZ4_compress_safe() on GCC64. */ const size_t dec32table[] = {0, 3, 2, 3, 0, 0, 0, 0}; static const size_t dec64table[] = {0, 0, 0, (size_t) - 1, 0, 1, 2, 3}; /* Special cases */ /* targetOutputSize too high => decode everything */ if ((partialDecoding) && (oexit > oend - MFLIMIT)) oexit = oend - MFLIMIT; /* Empty output buffer */ if ((endOnInput) && unlikey(outputSize == 0)) return ((inputSize == 1) && (*ip == 0)) ? 0 : -1; if ((! endOnInput) && unlikely(outputSize == 0)) return (*ip == 0 ? 1 : -1); /* Main loop,每次循环解压一个序列 */ while(1) { unsigned token; size_t length; /* get runlength,获取literal length */ token = *ip++; if ((length = (token >> ML_BITS)) == RUN_MASK) { unsigned s = 255; while (((endOnInput) ? ip < iend : 1) && (s == 255)) { s = *ip++; length += s; } } /* copy literals */ cpy = op + length; if (((endOnInput) && ((cpy > (partialDecoding ? oexit : oend - MFLIMIT)) || (ip + length > iend - (2+1+LASTLITERALS)))) || ((! endOnInput) && (cpy > oend - COPYLENGTH))) { if (partialDecoding) { if (cpy > oend) goto _output_error; /* write attempt beyond end of output buffer */ if ((endOnInput) && (ip + length > iend)) goto _out_error; /* read attempt beyond end of input buffer */ } else { if ((! endOnInput) && (cpy != oend)) goto _output_error; /* block decoding must stop exactly there */ if ((endOnInput) && ((ip + length != iend) || (cpy > oend))) goto _output_error; /* input must be consumed */ } memcpy(op, ip, length); ip += length; op += length; break; /* 注意,这里是退出口 */ } LZ4_WILDCOPY(op, ip, cpy); ip -= (op - cpy); op = cpy; /* 拷贝literals */ /* get offset,获取偏移值 */ LZ4_READ_LITTLEENDIAN_16(ref, cpy, ip); ip += 2; /* offset outside destination buffer */ if ((prefix64k == noPrefix) && unlikely(ref < (BYTE *const) dest)) goto _output_error; /* get matchlength,获取match length */ if ((length = (token & ML_MASK)) == ML_MASK) { /* Ensure enough bytes remain for LASTLITERALS + token */ while((! endOnInput) || (ip < iend - (LASTLITERALS + 1))) { unsigned s = *ip++; length += s; if (s == 255) continue; break; } } /* copy repeated sequence,拷贝匹配match */ if unlikely((op - ref) < (int) STEPSIZE) { /* 匹配和自身重叠的情况,拷贝STEPSIZE大小 */ const size_t dec64 = dec64table[(sizeof(void *) == 4 ? 0 : op - ref]; op[0] = ref[0]; op[1] = ref[1]; op[2] = ref[2]; op[3] = ref[3]; op += 4; ref += 4; ref -= dec32table[op - ref]; A32(op) = A32(ref); op += STEPSIZE - 4; ref -= dec64; } else LZ4_COPYSTEP(op, ref); cpy = op + length - STEPSIZE + 4; /* match length + 4才是实际match大小*/ if unlikely(cpy > oend - COPYLENGTH - (STEPSIZE - 4)) { if (cpy > oend - LASTLITERALS) goto _output_error; /* last 5 bytes must be literals */ LZ4_SECURECOPY(op, ref, (oend - COPYLENGTH)); while(op < cpy) *op++ = *ref++; op = cpy; continue; } LZ4_WILDCOPY(op, ref, cpy); op = cpy; /* correction */ } /* end of decoding */ if (endOnInput) return (int) (((char *)op) - dest); /* Nb of output bytes decoded,解压得到了多少字符 */ else return (int) (((char *)op) - source); /* Nb of input bytes read,读了多少个压缩字符 */ /* Overflow error detected */ _output_error: return (int) (-(((char *) ip) - source)) - 1; /* 负号表示出错,值表示Nb of input bytes read */ }
符合以下任一条件退出:
1. endOnInputSize
1.1 partial、cpy > oexit。出错情况:cpy > oend,或ip + length > iend。
1.2 full、cpy > oend - 12、ip + length == iend。出错情况:ip + length != iend,或cpy > oend。
1.3 ip + length > iend - 2 - (1 + 5)
1.3.1 partial。出错情况:cpy > oend,或ip + length > iend。
1.3.2 full、ip + length == iend。出错情况:ip + length != iend,或cpy > oend。
2. endOnOutputSize
2.1 cpy > oend - 8。
2.1.1 partial。出错情况:cpy > oend。
2.1.2 full、cpy == oend。出错情况:cpy != oend。
LZ4使用
make / make clean
得到可执行程序:lz4、lz4c
Usage:
./lz4 [arg] [input] [output]
input : a filename
Arguments :
-1 : Fast compression (default)
-9: High compression
-d : decompression (default for .lz4 extension)
-z : force compression
-f : overwrite output without prompting
-h/-H : display help/long help and exit
LZ4的输入只能为文件,不能为文件夹,毕竟一般压缩工具都不提供tar功能的。
-b file1 [file2] 可以用来测量压缩和解压速度。
比较遗憾的是,没有看可以指定线程数的参数,所以接下来没有测试多线程环境下的效果。
LZ4测试
Xeon E5504 @ 2.00GHz,X84_64,8核CPU,只用了一个。
(1) 速度
可以看到压缩速度和解压速度都很快,而且对日志文件的压缩比相当高。
(2) 压缩比
原始文件为linux-3.6.10.tar,大小为467MB。
用gzip压缩后为linux-3.6.10.tar.gz,大小为101MB,压缩比为21.62%。
用bzip2压缩后为linux-3.6.10.tar.bz2,大小为79MB,压缩比为16.91%。
用lz4压缩后为linux-3.6.10.tar.lz4,大小为166MB,压缩比为35.38%。
用lz4_HC压缩后为linux-3.6.10.tar.lz4,大小为117MB,压缩比为25.03%。
可以看到在压缩比上:lz4 < lz4_HC < gzip < bzip2。
然而在压缩过程中,笔者可以感觉到lz4的压缩时间比其它的要少一个数量级,几乎是瞬间完成:)
所以LZ4的优势在于压缩和解压速度,而不是压缩比。