4. 变长编码(VByte):原理、算法与倒排列表应用
VByte(Variable Byte)是倒排索引压缩中最经典的变长编码之一。它用每个字节的最高位(continuation bit)标记当前字节是否为某个整数的最后一个字节,从而用 1~5 个字节表示任意 32 位无符号整数。VByte 在解压速度上远优于位对齐编码(如 Golomb、Rice),且压缩比适中,是搜索引擎中“以速度换空间”的常用方案。
4.1 VByte 编码原理
VByte 将每个整数拆分为若干 7 位有效数据块,每个数据块存入一个字节的低 7 位,字节的最高位作为“延续位”:
- 延续位 = 1:当前字节是整数的最后一个字节。
- 延续位 = 0:后面还有后续字节。
编码时从整数的低位开始,每次取 7 位,直到所有位处理完毕。最后一个字节的延续位置 1,其余字节的延续位置 0。解码时依次读取字节,检查延续位,若为 0 则继续读取,若为 1 则拼接出完整整数。
关键特性: VByte 是字节对齐的,每个编码单元都是完整字节,因此解码时无需位操作,仅需移位和按位或运算,在现代 CPU 上可达到 1~2 GB/s 的解压吞吐。
4.2 编码与解码算法步骤
4.2.1 编码算法
- 输入:32 位无符号整数
n。 - 循环:当
n >= 128时,取n的低 7 位,与 0x80 按位或(延续位=0),存入输出缓冲区;n >>= 7。 - 最后字节:将剩余的
n与 0x80 按位或(延续位=1),存入输出缓冲区。 - 输出:字节序列,长度 1~5 字节。
// C 风格 VByte 编码
void vbyte_encode(uint32_t n, uint8_t* &out) {
while (n >= 128) {
*out++ = (n & 0x7F) | 0x80; // 延续位=0
n >>= 7;
}
*out++ = n | 0x80; // 延续位=1
}
4.2.2 解码算法
- 输入:VByte 编码的字节流。
- 循环:读取一个字节
b,取低 7 位拼接到结果val的高位;若b & 0x80 == 0,继续读取下一个字节;若b & 0x80 != 0,解码结束。 - 输出:还原的 32 位整数
val。
// C 风格 VByte 解码
uint32_t vbyte_decode(const uint8_t* &in) {
uint32_t val = 0;
int shift = 0;
uint8_t b;
do {
b = *in++;
val |= (uint32_t)(b & 0x7F) << shift;
shift += 7;
} while ((b & 0x80) == 0);
return val;
}
性能要点: 解码循环中每次迭代处理一个字节,无分支预测惩罚(延续位检查是固定模式),且移位量每次增加 7,编译器可优化为常量加法。实测单核解码速度可达 1.5 GB/s 以上。
4.3 VByte 在倒排列表中的应用
倒排列表存储的是文档 ID 序列和词频(TF)序列。直接存储原始 ID 会导致大量冗余,因此搜索引擎通常先对文档 ID 进行 差值(d-gap)转换,再对差值序列进行 VByte 编码。
| 步骤 | 操作 | 示例(文档 ID 序列) |
|---|---|---|
| 1. 原始 ID | 直接存储 | [42, 100, 101, 205, 1000] |
| 2. d-gap 转换 | 相邻 ID 求差 | [42, 58, 1, 104, 795] |
| 3. VByte 编码 | 对每个差值编码 | 42→1字节, 58→1字节, 1→1字节, 104→2字节, 795→2字节 |
| 4. 存储 | 字节流写入倒排文件 | 总字节数:1+1+1+2+2 = 7 字节 |
词频(TF)通常与文档 ID 交替存储或使用独立流。由于词频值一般较小(多数为 1~10),VByte 编码后大多只需 1 个字节,压缩效率极高。
4.4 VByte 的优缺点与适用场景
| 维度 | 评价 |
|---|---|
| 压缩比 | 中等。小整数(1~127)仅 1 字节,大整数(2^21~2^28)需 4 字节。平均压缩比约 2~3:1。 |
| 解码速度 | 极快。字节对齐、无位操作、无分支预测惩罚,单核可达 1.5 GB/s+。 |
| 实现复杂度 | 极低。编码/解码各约 5~10 行代码,无复杂数学运算。 |
| 适用场景 | 对延迟敏感、需要快速随机访问的搜索服务;倒排列表较短或差值较小的场景。 |
| 局限性 | 大整数(如 d-gap 超过 2^21)会膨胀到 4 字节,压缩比不如位对齐编码(如 Simple-9、PForDelta)。 |
尾延迟影响提示: VByte 的解码速度高度稳定,几乎不受数据分布影响。在压测中,VByte 的尾延迟(p99/p999)通常比位对齐编码低 30%~50%,但压缩后倒排文件体积可能增大 10%~20%,导致 I/O 次数增加。实际选型需在“压缩比”与“解码速度”之间权衡。