Proto 序列化不是规范的

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

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

  • 使用序列化后的 proto 作为哈希表中的键
  • 对序列化后的 proto 进行指纹识别或校验和计算
  • 比较序列化后的载荷作为检查消息是否相等的方式

不幸的是,protobuf 序列化不是(也不能是)规范的。有一些显著的例外,例如 MapReduce,但总的来说,您通常应该将 proto 序列化视为不稳定的。本页解释了原因。

确定性不等于规范性

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

  1. protobuf schema 以任何方式发生变化。
  2. 正在构建的应用程序以任何方式发生变化。
  3. 二进制文件使用不同的标志构建(例如 opt 与 debug)。
  4. protobuf 库已更新。

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

序列化输出可能发生变化的原因有很多。以上列表并不详尽。其中一些是问题空间中固有的困难,即使我们想保证规范序列化,也会使其变得低效或不可能。另一些是我们有意未定义的事项,以便为优化留出机会。

稳定序列化的固有障碍

Protobuf 对象会保留未知字段,以提供向前和向后兼容性。未知字段的处理是规范序列化的主要障碍。

在线上格式中,字节字段和嵌套子消息使用相同的 wire type。这种模糊性使得无法正确规范化存储在未知字段集中的消息。由于完全相同的内容可能是其中任何一种,因此无法知道是将其作为消息处理并向下递归,还是不这样做。

为提高效率,实现通常在已知字段之后序列化未知字段。然而,规范序列化要求根据字段编号将未知字段与已知字段交错排列。这将给所有用户带来显著的效率和代码大小成本,即使是那些不需要此功能的用户。

有意未定义的事项

即使规范序列化是可行的(也就是说,如果我们能解决未知字段问题),我们仍有意未定义序列化顺序,以便为更多优化机会留出空间:

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

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