为啥protobuf序列化“oneof”消息使用if-else

Posted

技术标签:

【中文标题】为啥protobuf序列化“oneof”消息使用if-else【英文标题】:Why protobuf serialize "oneof" message use if-else为什么protobuf序列化“oneof”消息使用if-else 【发布时间】:2018-04-20 08:09:45 【问题描述】:

我有一个这样的消息定义:

message Command
  oneof type
    Point       point       = 1;
    Rotate      rotate      = 2;
    Move        move        = 3;
    ... //about 100 messages
   

然后protoc生成SerializeWithCachedSizes函数:

void Command::SerializeWithCachedSizes(
    ::google::protobuf::io::CodedOutputStream* output) const 
  // @@protoc_insertion_point(serialize_start:coopshare.proto.Command)
  ::google::protobuf::uint32 cached_has_bits = 0;
  (void) cached_has_bits;

  // .coopshare.proto.Point point = 1;
  if (has_point()) 
    ::google::protobuf::internal::WireFormatLite::WriteMessageMaybeToArray(
      1, *type_.point_, output);
  

  // .coopshare.proto.Rotate rotate = 2;
  if (has_rotate()) 
    ::google::protobuf::internal::WireFormatLite::WriteMessageMaybeToArray(
      2, *type_.rotate_, output);
  

  // .coopshare.proto.Move move = 3;
  if (has_move()) 
    ::google::protobuf::internal::WireFormatLite::WriteMessageMaybeToArray(
      3, *type_.move_, output);
  

“oneof”消息将特定类型保存在_oneof_case_中。我认为使用 switch-case 更有效。

但是为什么 protobuf 还是会生成这样的代码呢?

【问题讨论】:

编译器很聪明,所以做任何一个都可能无关紧要。编译器能够将一长串if/else 块转换为查找表。如果您尝试使用一个小的switch 块来编写程序,您会看到编译器会将其转换为几个条件跳转。生成工具可能会这样做,因为它更容易生成。 【参考方案1】:

Oneofs 在内部处理类似于可选字段。事实上,descriptor.proto 将它们表示为一组可选字段,只有一个额外的 oneof_index 表示它们属于一起。这是一个合理的选择,因为它允许在添加任何特殊支持之前立即将 oneofs 与许多库一起使用。

我假设 C++ 代码生成器对可选字段和 oneof 使用相同的结构。

switch-case 可能会生成更高效的代码,在这种情况下,建议将其作为对 protobuf 项目的改进会很有用。然而,就像 Jorge Bellón 在 cmets 中指出的那样,编译器完全有可能自动优化这个结构。必须进行测试和基准测试才能确定。

【讨论】:

以上是关于为啥protobuf序列化“oneof”消息使用if-else的主要内容,如果未能解决你的问题,请参考以下文章

用于非 protobuf 类的 protobuf `oneof` 功能的 C++ 实现

如果子消息没有字段,如何在 protobuf 消息上分配 oneof 字段?

为啥我的 Protobuf 消息(在 Python 中)忽略零值?

golang使用protobuf中的oneof

Protobuf-net - 如何使用 oneof

.proto 文件中带有 oneof 的 Protobuf-net