Proto 序列化不是规范的
解释序列化如何工作以及为何它不是规范的。
许多人希望序列化后的 proto 能够规范地表示该 proto 的内容。用例包括:
- 使用序列化后的 proto 作为哈希表中的键
- 对序列化后的 proto 进行指纹识别或校验和计算
- 通过比较序列化的有效负载来检查消息是否相等
不幸的是,protobuf 序列化不是(也不能是)规范的。有一些值得注意的例外,例如 MapReduce,但总的来说,您通常应该将 proto 序列化视为不稳定的。本页解释了原因。
确定性不等于规范性
确定性序列化不是规范的。序列化器可能因多种原因生成不同的输出,包括但不限于以下变化:
- protobuf 模式(schema)以任何方式发生变化。
- 正在构建的应用程序以任何方式发生变化。
- 二进制文件使用不同的标志(例如 opt 与 debug)构建。
- protobuf 库已更新。
这意味着序列化后 proto 的哈希值是脆弱的,并且在时间或空间上都不稳定。
序列化输出可能发生变化的原因有很多。以上列表并非详尽无遗。其中一些是问题领域固有的困难,即使我们想保证规范序列化,也会使其变得低效或不可能。另一些是我们有意未定义的事项,以便为优化留出机会。
稳定序列化的固有障碍
Protobuf 对象会保留未知字段,以提供向前和向后兼容性。未知字段无法被规范地序列化:
- 未知字段无法区分字节(bytes)和子消息(sub-messages),因为它们具有相同的线路类型(wire type)。这使得对存储在未知字段集中的消息进行规范化变得不可能。如果我们要进行规范化,我们需要递归进入未知的子消息,按字段编号对其字段进行排序,但我们没有足够的信息来做到这一点。
- 为了效率,未知字段总是序列化在已知字段之后。但规范序列化要求按字段编号将未知字段与已知字段交错排列。这将给每个人带来效率和代码大小的开销,即使是不使用该功能的人也是如此。
有意未定义的事项
即使规范序列化是可行的(也就是说,如果我们能解决未知字段的问题),我们也有意未定义序列化顺序,以便为更多的优化机会留出空间:
- 如果我们能证明一个字段在二进制文件中从未使用过,我们可以将其从模式中完全移除,并将其作为未知字段处理。这可以节省大量的代码大小和 CPU 周期。
- 可能存在通过将同一字段的向量一起序列化来进行优化的机会,尽管这会破坏字段编号的顺序。
为了给这类优化留出空间,我们希望在某些配置中有意打乱字段顺序,这样应用程序就不会不适当地依赖于字段顺序。