LZ4简介
LZ4 是无损压缩算法,提供每个核 大于 500 MB/s 的压缩速度,可通过多核 CPU 进行扩展。LZ4算法解压速度极快,单核解压速度达到GB/s,通常达到多核系统的 RAM 速度限制。
压缩速度可以动态调整,选择一个“加速”因子,牺牲压缩比以获得更快的速度。另外还提供了一个高压缩率函数LZ4_HC
,牺牲压缩时间换取更高的压缩比,但解压缩速度不会受影响。
LZ4实现了两种格式:
-
lz4_Block_format:原始LZ4块压缩格式
-
lz4_Frame_format:用于压缩任意大的数据流,或压缩任意大小的文件
Benchmarks
从官方提供的Benchmarks数据,可以看出**LZ4 default (v1.9.0)和LZ4 HC -9 (v1.9.0)**压缩率和压缩速度不一致,但是解压缩速度却相同。
Compressor | Ratio | Compression | Decompression |
---|---|---|---|
memcpy | 1.000 | 13700 MB/s | 13700 MB/s |
LZ4 default (v1.9.0) | 2.101 | 780 MB/s | 4970 MB/s |
LZO 2.09 | 2.108 | 670 MB/s | 860 MB/s |
QuickLZ 1.5.0 | 2.238 | 575 MB/s | 780 MB/s |
Snappy 1.1.4 | 2.091 | 565 MB/s | 1950 MB/s |
[Zstandard] 1.4.0 -1 | 2.883 | 515 MB/s | 1380 MB/s |
LZF v3.6 | 2.073 | 415 MB/s | 910 MB/s |
[zlib] deflate 1.2.11 -1 | 2.730 | 100 MB/s | 415 MB/s |
LZ4 HC -9 (v1.9.0) | 2.721 | 41 MB/s | 4900 MB/s |
[zlib] deflate 1.2.11 -6 | 3.099 | 36 MB/s | 445 MB/s |
LZ4 Block Format
LZ4 是一种 LZ77 型压缩器,具有固定的、面向字节的编码格式,
块格式
LZ4压缩块由序列组成。分为三个区域:
- Literals:指没有重复、首次出现的字节流,即不可压缩的部分
- Match:指重复项,可以压缩的部分
- Token:记录literal长度,match长度。
简单的示例如下:
输入:abcde_bcdefgh_abcdefghxxxxxxx
输出:tokenabcde_(5,4)fgh_(14,5)fghxxxxxxx
格式:[token]literals(offset,match length)[token]literals(offset,match length)....
Token
令牌。一个字节,划分两个4位的字段:高4位为字面长度(Literal length),低4位为匹配长度(Match length)。
-
高4位:4个位只能表示0-15,再多的话就需要再增加一些字节(高4位为15表示需要增加接下来字节),每个增加的字节可表示0-255。如果为255,表示还有增加一个字节,直到字节中不满255。因此没有大小限制。举例如下:
字面长度为48表示:Token高4位为15,下一字节为33(48-15);
字面长度为280表示:Token高4位为15,下一字节为255,再下一字节为10(280-15-255);
字面长度为15表示:Token高4位为15,下一字节为0(必须输出零,15-15);
-
低4位:同理高4位,只是匹配长度放在offset之后
Literal length
字面长度字段,0-n字节
Literals
字面量本身字段,字面量可能为0
Offset
两字节的偏移量,小端格式,表示匹配到向前偏移长度。例如3表示当前位置向前有3个数据是匹配的(重复的)。最大值offset
为 65535,65536 及以上无法编码。请注意,0 是无效的偏移值。这种值的存在表示无效(损坏)块。
Match length
匹配长度字段,0-n字节。注意这里的0意味着复制操作将是最小的,即匹配的最小长度minmatch为4(Token低4位+4才为match length)。因此 0 值表示 4 个字节,15 值表示 19+ 个字节。
结束块限制
结束块有一些特定限制:
- 最后一个序列只包含字面量。
- 输入的最后 5 个字节始终是字面量。输入的最后 5 个字节始终是文字。
- 特别地:如果输入小于 5 个字节,则只有一个序列,它包含整个输入作为字面量。空输入可以用零字节表示,解释为没有字面量和匹配的最终标记。
- 最后一个匹配必须在块结束前至少 12 个字节开始。最后一场比赛是倒数第二场比赛的一部分。紧随其后的是最后一个序列,它只包含字面量。
- 请注意,因此,不能压缩 < 13 字节的独立块,因为匹配必须复制“某些东西”,因此它至少需要一个前字节。
- 但是,当一个块可以引用另一个块的数据时,它可以立即以匹配而不是字面量开始,因此可以压缩正好 12 个字节的块。
当一个块不遵守这些结束条件时,允许一致的解码器将该块视为不正确而拒绝。
这些规则是为了确保与各种历史解码器的兼容性,这些解码器在面向速度的设计中依赖这些条件。
LZ4 Frame Format
介绍
定义一种无损压缩数据格式,与CPU类型、操作系统、文件系统和字符集无关,适合文件压缩,管道和流压缩。
对于任意长度顺序呈现的输入数据流,支持部分数据的压缩和解压,因此可以用于数据通信。该格式使用 LZ4 压缩方法和xxHash-32 校验(可选,用于检测数据损坏)。
定义的数据格式不允许随机访问压缩数据。
一般结构
MagicNb | F. Descriptor | Block | (…) | EndMark | C. Checksum |
---|---|---|---|---|---|
4 bytes | 3-15 bytes | 4 bytes | 0-4 bytes |
-
MagicNb:Magic Number,魔法数,4字节,小端,值为0x184D2204
-
F. Descriptor:Frame Descriptor,帧描述符,3-15字节,最为重要,Magic_Number 和_Frame_Descriptor_放在一起常叫做LZ4 Frame Header,长度为7-19字节
-
Block:Data Blocks,数据块,存放的压缩数据
-
EndMark:结束标记,在最后一个数据块之后,值为32位的0x00000000
-
C.Checksum:Content Checksum,内容校验和验证这个数据内容是否已正确解码。其是xxHash-32 算法计算的摘要值,该算法使用原始数据(解码后)作为输入,0作为种子。只有在帧描述符Frame Descriptor中设置了相关标志时,才会出现Content_Checksum。Content_Checksum用于验证:所有块都以正确的顺序完成传输,没有错误,编码/解码过程没有产生失真。推荐使用它。EndMark和Content_Checksum放在一起常叫做LZ4 Frame Footer,长度为4-8字节
-
Frame Concatenation:在某些情况下,最好附加多个帧,例如为了将新数据添加到现有压缩文件而不重新构建它。在这种情况下,每个帧都有自己的一组描述符标志。每个帧都被认为是独立的,帧之间的唯一关系是它们的顺序。在单个流或文件中解码多个连接帧的能力不在LZ4规范范围内。
Frame Descriptor
FLG | BD | (Content Size) | (Dictionary ID) | HC |
---|---|---|---|---|
1 byte | 1 byte | 0 - 8 bytes | 0 - 4 bytes | 1 byte |
描述符使用最少 3 个字节,最多 15 个字节,具体取决于可选参数。
FLG byte
BitNb | 7-6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|
FieldName | Version | B.Indep | B.Checksum | C.Size | C.Checksum | Reserved | DictID |
- Version:版本号。占两位,必须设置为01
- B.Indep:Block Independence flag,块独立标志。如果为1,则块是独立的,否则为0,每个块都依赖于先前的块(最大 LZ4 窗口大小,即 64 KB)。在这种情况下,有必要按顺序解码所有块。块相关性提高了压缩率,尤其是对于小块。但是另一方面,将无法随机访问或多线程解码。
- B.Checksum:Block checksum flag,块校验标志。如果为1,则每个数据块后面跟一个4字节的checksum,通过对原始(压缩后)数据块使用 xxHash-32 算法计算得出,目的是在解码之前立即检测数据损坏(存储或传输错误)。B.Checksum块校验和的使用是可选的。
- C.Size:Content Size flag,块大小标志。如果为1,则BD之后放未压缩的数据大小字段,其是 8 字节无符号小端值的形式。C.Size也是可选的。
- C.Checksum:Content checksum flag,内容校验和标志。如果为1,在EndMark之后添加32位的内容校验和。
- DictID:Dictionary ID flag,字典ID标志。如果为1,在Content Size之后,放4字节的Dict-ID。
BD byte
BitNb | 7 | 6-5-4 | 3-2-1-0 |
---|---|---|---|
FieldName | Reserved | Block MaxSize | Reserved |
-
Block MaxSize:Block Maximum Size,块最大长度。该信息有助于解码器分配内存。这里的大小是指原始(未压缩)数据大小的最大值。块最大大小是下表中的一个值:
0 1 2 3 4 5 6 7 N/A N/A N/A N/A 64 KB 256 KB 1 MB 4 MB 解码器可能拒绝分配比较大的的块空间。
-
Reserverd:保留位,必须为0
Content Size bytes
这是原始(未压缩)大小。此信息是可选的,并且仅在设置了相关标志时才出现。内容大小使用无符号 8 字节提供,最大为 16EB容量。格式为小端。此值通常用于显示或内存分配。它可以被解码器跳过,或用于验证内容的正确性。
Dictionary ID
仅当设置了相应标志时才存在字典 ID。它是一个无符号的 32 位值,使用 little-endian 约定存储。字典对于压缩短输入序列很有用。压缩器可以利用字典上下文以更紧凑的方式对输入进行编码。它作为一种“已知前缀”工作,压缩器和解压缩器都使用它来“预热”参考表。
解压缩器可以使用 Dict-ID 标识符来确定必须使用哪个字典才能正确解码数据。压缩器和解压缩器必须使用完全相同的字典。假定 32 位 dictID 唯一标识一个字典。
在单个框架内,可以定义单个字典。当帧描述符定义独立的块时,每个块都将使用相同的字典进行初始化。如果帧描述符定义了链接块,则字典只会在帧开始时使用一次。
HC byte
- hc:Header Checksum,头校验和。其是Frame Descriptor的校验和,值为xxh32(使用0为种子)的第二字节:
(xxh32()>>8) & 0xFF
。头检验和检验描述符是否正确,头校验和是信息性的,可以跳过。
Data Blocks
Block Size | data | (Block Checksum) |
---|---|---|
4 bytes | 0 - 4 bytes |
Block Size
占用4字节,小端格式。如果最高位为1,该块未压缩;最高位为0,则使用LZ4 块格式规范对块进行 LZ4 压缩。其他位是数据段的长度大小(以字节为单位),该大小不包括校验和(如果存在)。
Block_Size不得大于Block_Maximum_Size。如果源数据不可压缩,可能存在Block_Size等于Block_Maximum_Size。在这种情况下,必须使用未压缩格式传递此类数据块。
值0x00000000
无效,而是表示EndMark。请注意,这与 (最高位设置为1) 的值不同0x80000000
,后者是大小为 0(空)的未压缩块,它是有效的,因此不会结束帧。请注意,如果启用了Block_checksum,则即使是空块也必须后跟 32 位块校验和。
Data
要解码的实际数据所在的位置。它可能会被压缩,也可能不会被压缩,这取决于之前的字段指示。压缩时,数据必须遵守LZ4 块格式规范。请注意,块不一定是满的。未压缩的数据大小可以是最大为Block_Maximum_Size,因此它包含的数据可能少于最大块大小。
Block Checksum
仅在设置了相关标志时才存在。这是一个 4 字节的校验和值,采用小端格式,使用xxHash-32 算法对原始(未解码)数据块进行计算,种子为零,目的是在解码之前检测数据损坏(存储或传输错误)。
Skippable Frames
Magic Number | Frame Size | User Data |
---|---|---|
4 bytes | 4 bytes |
可跳过的帧,其允许将用户定义的数据集成到串联的帧流中。它的设计非常简单,唯一的目标是让解码器快速跳过用户定义的数据并继续解码。
为了便于识别,不鼓励使用可跳过的帧开始连接帧流。如果需要在可跳过帧中使用一些用户数据,建议从零字节 LZ4 帧开始,然后是可跳过帧,将使文件类型标识符更容易。
Magic Number
魔法数,4字节,小端格式。值为0x184D2A5X,表示从 0x184D2A50 到 0x184D2A5F 的任何值。所有 16 个值都可用于识别可跳过的帧。
Frame Size
这是接下来用户数据的大小(以字节为单位)(不包括魔法数或大小字段本身)。4 字节,小端格式,无符号 32 位。这意味着用户数据不能大于 (232-1) 字节。
User Data
用户数据,解码器会跳过这些数据。
压缩示例
数据原文
hello david, hello lily, hello tom, hello lucy, hello bob
保存到文件data
$ echo "hello david, hello lily, hello tom, hello lucy, hello bob" > data
压缩前数据
数据原文的hex形式如下。
$ hexdump -C data
00000000 68 65 6c 6c 6f 20 64 61 76 69 64 2c 20 68 65 6c |hello david, hel|
00000010 6c 6f 20 6c 69 6c 79 2c 20 68 65 6c 6c 6f 20 74 |lo lily, hello t|
00000020 6f 6d 2c 20 68 65 6c 6c 6f 20 6c 75 63 79 2c 20 |om, hello lucy, |
00000030 68 65 6c 6c 6f 20 62 6f 62 0a |hello bob.|
0000003a
压缩后数据
使用帧格式,执行lz4命令进行压缩如下。
$ lz4 data data.lz4
数据显示如下。
$ hexdump -C data.lz4
00000000 04 22 4d 18 64 40 a7 29 00 00 00 d2 68 65 6c 6c |."M.d@.)....hell|
00000010 6f 20 64 61 76 69 64 2c 20 0d 00 44 6c 69 6c 79 |o david, ..Dlily|
00000020 0c 00 34 74 6f 6d 0b 00 34 6c 75 63 17 00 50 20 |..4tom..4luc..P |
00000030 62 6f 62 0a 00 00 00 00 90 ba d9 c9 |bob.........|
0000003c
数据分析
MagicNb
04 22 4D 18
魔法数为0x184D2204
F. Descriptor
64 40 a7
FLG | BD | (Content Size) | (Dictionary ID) | HC |
---|---|---|---|---|
64 | 40 | 无 | 无 | 1 byte |
-
FLG:0x64=0110 0100
7-6 5 4 3 2 1 0 Version B.Indep B.Checksum C.Size C.Checksum Reserved DictID 01 1 0 0 1 0 0 版本01 块独立 无Block校验 无块大小 有内容校验 保留 无字典ID -
BD:0x40,块最大大小为1MB
-
Content Size:无
-
Dictionary ID:无
-
HC:0xa7
Block
00000007 29 00 00 00 d2 68 65 6c 6c 6f 20 64 61 76 69 64 |)....hello david|
00000017 2c 20 0d 00 44 6c 69 6c 79 0c 00 34 74 6f 6d 0b |, ..Dlily..4tom.|
00000027 00 34 6c 75 63 17 00 50 20 62 6f 62 0a |.4luc..P bob.|
00000034
Block Size | data | (Block Checksum) |
---|---|---|
4 bytes | 0 - 4 bytes | |
0x00000029 | d2 68 65 6c … 62 6f 62 0a | 无 |
-
Block Size:0x00000029,最高为0,表示压缩,data长度为0x29=41
-
data:41字节的压缩数据
0000000b d2 68 65 6c 6c 6f 20 64 61 76 69 64 2c 20 0d 00 |.hello david, ..| 0000001b 44 6c 69 6c 79 0c 00 34 74 6f 6d 0b 00 34 6c 75 |Dlily..4tom..4lu| 0000002b 63 17 00 50 20 62 6f 62 0a |c..P bob.| 00000034
-
序列1
d2 68 65 6c 6c 6f 20 64 61 76 69 64 2c 20 0d 00
-
Token:0xd2,字面长度0x0d,匹配长度0x02(实际为0x06)
-
Literals:字面量13字节
68 65 6c 6c 6f 20 64 61 76 69 64 2c 20
-
Offset:0x000d,向前偏移13字节
-
-
序列2
44 6c 69 6c 79 0c 00
-
Token:0x44,字面长度0x04,匹配长度0x04(实际为0x08)
-
Literals:字面量4字节
6c 69 6c 79
-
Offset:0x000c,向前偏移12字节
-
-
序列3
37 74 6f 6d 0b 00
-
Token:0x34,字面长度0x03,匹配长度0x04(实际为0x08)
-
Literals:字面量3字节
74 6f 6d
Offset:0x000b,向前偏移11字节
-
-
序列4
34 6c 75 63 17 00
-
Token:0x34,字面长度0x03,匹配长度0x04(实际为0x08)
-
Literals:字面量3字节
6c 75 63
Offset:0x0017,向前偏移23字节
-
-
序列5(最后一个序列)
50 20 62 6f 62 0a
-
Token:0x50,字面长度0x05,匹配长度0x00
-
Literals:字面量5字节
20 62 6f 62 0a
-
-
-
Block Checksum:无
EndMark
00 00 00 00
结束标识0x00000000
C. Checksum
90 ba d9 c9
- Literals:字面量3字节
```bash
6c 75 63
```
Offset:0x0017,向前偏移23字节
-
序列5(最后一个序列)
50 20 62 6f 62 0a
-
Token:0x50,字面长度0x05,匹配长度0x00
-
Literals:字面量5字节
20 62 6f 62 0a
-
- Block Checksum:无
EndMark
00 00 00 00
结束标识0x00000000
C. Checksum
90 ba d9 c9