Ink & Switch 公开介绍了 bijou64。它是为 Subduction CRDT 同步协议开发的 u64 可变长度整数编码。
有意思的地方不只是“比 LEB128 快”。更关键的是,它想解决一个更细的协议问题:同一个整数,如果能写成多种字节序列,签名、内容寻址、去重和同步就可能看到不同的字节。
比如 0。在 LEB128 里,0 可以写成 0x00,也可以写成 0x80 0x00。数值一样,字节不同。
普通解析器可能只觉得这是“过长编码”。但在签名和内容寻址里,字节就是边界。差一个字节,哈希、签名、去重结果都可能变。
我的判断是:bijou64 真正值得看的点,是把“规范编码”从运行时检查,尽量前移到格式结构里。它不是 LEB128 的通用替代品,但很适合提醒新协议设计者:格式能少留一个坑,就不要把坑交给每个实现去填。
LEB128 的问题,不是慢,而是同值多码
LEB128 很成熟。Protocol Buffers、DWARF 等生态里都能看到类似 varint 思路:每个字节拿 7 位放数据,最高位表示后面还有没有字节。
这个设计紧凑,也好实现。但它天然有一个规范化问题:如果实现不拒绝过长编码,同一个值就可能有多种字节表示。
0 是最直观的例子:
| 数值 | 可能的 LEB128 字节表示 | 问题 |
|---|---|---|
| 0 | 0x00 | 最短表示 |
| 0 | 0x80 0x00 | 数值仍是 0,但字节变了 |
| 0 | 多个 0x80 后接 0x00 | 更长的非规范表示 |
这不是说 LEB128 有一个专属安全漏洞。更准确地说,很多可变长度编码、序列化格式和签名协议都会遇到同类风险:值相等,不代表字节相等。
历史上,ASN.1、JWT、比特币交易可塑性等案例都反复说明过一件事:规范化规则写在文档里,不等于每个实现都会永远做对。
对二进制协议设计者,这意味着一个很现实的动作:如果格式还没冻结,就要尽早决定“唯一表示”是格式保证,还是靠所有解码器拒绝非规范输入。
对做签名、内容寻址、CRDT 同步一致性的工程师,动作更具体:不要只测 decode 后的值相等,还要测字节级规范性。否则跨语言实现一多,问题会从单元测试里漏出去。
bijou64 用首字节把长度和唯一性锁住
bijou64 的结构很直接。
首字节 0–247 直接表示对应整数。248–255 不再表示普通数值,而是作为长度标签,告诉解码器后面还有多少 payload 字节。
后续 payload 再配合 offset。比如 0xF8 0x00 表示 248,而不是重复表示 0。这样一来,小整数有直表,大整数有固定区间,每个 u64 范围内的整数都对应唯一编码。
可以把差异压成一张表:
| 维度 | LEB128 | bijou64 | 对协议设计的影响 |
|---|---|---|---|
| 小整数 | 7 位分组,continuation bit 续写 | 0–247 单字节直表 | 常见小值仍紧凑 |
| 长度判断 | 逐字节看最高位 | 首字节 248–255 标记总长度 | 解码路径更可预测 |
| 规范性 | 通常靠拒绝过长编码 | 用 offset 避免重复表示 | 少一个易漏的规范化检查 |
| 边界检查 | 取决于实现 | 9 字节最大档仍需 u64 上界检查 | 不是完全免检查 |
这里要注意一个限制:bijou64 不是“完全不用检查”。最大 9 字节档仍然需要确认没有越过 u64 上界。
它的改进点更准确地说是:把一大类重复表示问题从格式上消掉。剩下的边界检查仍然要做。
这对新协议更有用。尤其是还在设计同步日志、块格式、哈希输入、签名载荷的人,可以把 bijou64 当成一个候选方案,而不是等到格式稳定后再补一层 canonicalization 规则。
快在哪里,什么时候不该换
公开基准显示,bijou64 解码大约比 LEB128 快 2–10 倍。在 full-u64 均匀分布下,bijou64 处理 4096 个值约 3 微秒,LEB128 约 30 微秒。
原因不玄。bijou64 从首字节就知道总长度,payload 是连续大端整数。LEB128 要逐字节扫描 continuation bit,还要对 7 位分组做掩码和移位。
但这个跑分不能读成“全面替代”。已公开基准主要来自 Apple M2 Pro、AMD Zen 5,另有 Zen 3 的观察。不同 CPU、语言、编译器和内存访问模式下,结果可能变。
编码端也不是全胜。原文提到,在 248–65,535 的 small 分布里,LEB128 约快 1.24 倍。
空间也一样。bijou64 不是所有场景最省字节的 varint。在 realistic workloads 中,它和 LEB128 的线缆字节数通常只差几个百分点。
所以更现实的路线是:
| 场景 | 更合理的动作 |
|---|---|
| 新二进制协议、存储格式还没冻结 | 可以评估 bijou64,重点看唯一编码和跨实现测试 |
| 已部署多年、依赖 LEB128 的格式 | 不建议只为跑分迁移,除非规范化风险或解析成本已经很痛 |
| 签名、内容寻址、CRDT 同步载荷 | 优先检查是否存在同值多码问题,再决定是否换编码 |
| 只追求最小体积的场景 | 不要默认 bijou64 更省,必须按真实分布测 |
LEB128 的优势仍然很硬:实现多,工具链成熟,调试经验多,跨语言生态稳定。对很多项目来说,这些比微基准更值钱。
bijou64 目前有 Rust crate,采用 MIT / Apache-2.0 双许可,规范为 CC BY-SA 4.0,也有 Wasm/JavaScript wrapper。但它仍是新格式。缺的不是一张更漂亮的跑分图,而是独立实现、测试向量、互操作经验,以及恶意输入下的长期验证。
我更在意的观察点只有三个:有没有更多独立实现;跨语言测试向量是否覆盖边界值和非规范输入;真实同步负载里,唯一编码带来的收益能否抵消新格式的生态成本。
如果答案逐步变好,bijou64 会成为新协议里很稳的候选。如果只是为了“比 LEB128 快”,理由还不够厚。
