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