Proto 序列化并非规范化

解释序列化如何工作以及为什么它不是规范化的。

许多人希望序列化后的 proto 能够规范地表示该 proto 的内容。用例包括

  • 使用序列化后的 proto 作为哈希表中的键
  • 获取序列化后的 proto 的指纹或校验和
  • 比较序列化后的有效负载作为检查消息相等性的一种方法

不幸的是,protobuf 序列化不是(也无法是)规范化的。有一些值得注意的例外,例如 MapReduce,但总的来说,您应该通常将 proto 序列化视为不稳定的。此页面解释了原因。

确定性不等于规范化

确定性序列化不等于规范化。序列化程序可以出于多种原因生成不同的输出,包括但不限于以下变化

  1. protobuf 模式以任何方式更改。
  2. 正在构建的应用程序以任何方式更改。
  3. 二进制文件使用不同的标志构建(例如,优化版与调试版)。
  4. protobuf 库已更新。

这意味着序列化后的 proto 的哈希值很脆弱,并且在时间或空间上不稳定。

序列化输出可能发生更改的原因有很多。以上列表并不详尽。其中一些是问题空间中固有的难题,即使我们想要保证规范化序列化,也会使其效率低下或无法保证。其他一些是我们有意未定义的事项,以便能够进行优化机会。

稳定序列化中的固有障碍

Protobuf 对象保留未知字段以提供向前和向后兼容性。未知字段无法规范化序列化

  1. 未知字段无法区分字节和子消息,因为两者具有相同的线类型。这使得无法规范化存储在未知字段集中的消息。如果我们要规范化,我们需要递归进入未知子消息以按字段编号对其字段进行排序,但我们没有足够的信息来做到这一点。
  2. 出于效率考虑,未知字段始终在已知字段之后序列化。但是,规范化序列化需要根据字段编号将未知字段与已知字段交错。这将导致每个人都产生效率和代码大小开销,即使是不使用此功能的人。

故意未定义的事项

即使规范化序列化是可行的(也就是说,如果我们能够解决未知字段问题),我们也故意将序列化顺序未定义,以便能够获得更多优化机会

  1. 如果我们可以证明某个字段在二进制文件中从未使用过,我们可以将其完全从模式中删除并将其作为未知字段处理。这节省了大量的代码大小和 CPU 周期。
  2. 可以通过将同一字段的向量一起序列化来优化,即使这会破坏字段编号顺序。

为了为这样的优化留出空间,我们希望在某些配置中有意打乱字段顺序,以便应用程序不会不适当地依赖于字段顺序。