【问题标题】:protocol buffer GetRepeatedField (reflection) code optimization协议缓冲区GetRepeatedField(反射)代码优化
【发布时间】:2015-03-11 07:55:00
【问题描述】:

我正在使用协议缓冲区的反射功能在运行时读取消息字段值。

我拥有的原型:

package xapp.battle;
    message BATTLE_DATA {
        repeated AInfo aInfo = 1;
        repeated BInfo bInfo = 2;
        repeated CInfo cInfo = 3;
        // a lot other repeated messages
    }

    message AInfo {
        int32 test_field = 1;
        // ......
    }

    message BInfo {
        int32 test_field = 1;
        // ......
    }

    message CInfo {
        int32 test_field = 1;
        // ......
    }

我现在拥有的代码:

void DO_SOMETHING(messageName) {

    const Descriptor* pDescriptor = BATTLE_DATA->GetDescriptor();
    const FieldDescriptor* pMessageField = pDescriptor->FindFieldByName(messageName);
    const Reflection* pReflection = BATTLE_DATA->GetReflection();

    if (messageName == "aInfo") {
        const RepeatedPtrField<::xapp::battle::AInfo> repeated_ptr_field = pReflection->GetRepeatedPtrField<::xapp::battle::AInfo>(*BATTLE_DATA, pMessageField);
        for (int i = 0; i < repeated_ptr_field.size(); i ++) {
            ::xapp::battle::AInfo messageInfo = repeated_ptr_field.Get(i);
            // continue to read the test_field value of messageInfo
        }
    }
    else if (messageName == "bInfo") {
        const RepeatedPtrField<::xapp::battle::BInfo> repeated_ptr_field = pReflection->GetRepeatedPtrField<::xapp::battle::BInfo>(*BATTLE_DATA, pMessageField);
        for (int i = 0; i < repeated_ptr_field.size(); i ++) {
            ::xapp::battle::BInfo messageInfo = repeated_ptr_field.Get(i);
            // continue to read the test_field value of messageInfo
        }
    }
    else if (messageName == "CInfo") {
        const RepeatedPtrField<::xapp::battle::CInfo> repeated_ptr_field = pReflection->GetRepeatedPtrField<::xapp::battle::CInfo>(*BATTLE_DATA, pMessageField);
        for (int i = 0; i < repeated_ptr_field.size(); i ++) {
            ::xapp::battle::CInfo messageInfo = repeated_ptr_field.Get(i);
            // continue to read the test_field value of messageInfo
        }
    }
    // ......
    else {
            LOG("loadOneBin - Unknown messageName");
    }
}

此代码有效,但显然它不是最佳解决方案,因为有太多重复的“else-if”代码块。

我想要的是(至少摆脱那些“else-if”块):

const RepeatedPtrField<::xapp::battle::MESSAGE_NAME> repeated_ptr_field = pReflection->GetRepeatedPtrField<::xapp::battle::MESSAGE_NAME>(*BATTLE_DATA, pMessageField);
for (int i = 0; i < repeated_ptr_field.size(); i ++) {
    ::xapp::battle::MESSAGE_NAME messageInfo = repeated_ptr_field.Get(i);
    // continue to read the test_field value of messageInfo

}

GetRepeatedPtrField的源码:

template<typename PB>
inline const RepeatedPtrField<PB>& Reflection::GetRepeatedPtrField(
    const Message& message, const FieldDescriptor* field) const {
  return *static_cast<RepeatedPtrField<PB>* >(
      MutableRawRepeatedField(const_cast<Message*>(&message), field,
          FieldDescriptor::CPPTYPE_MESSAGE, -1,
          PB::default_instance().GetDescriptor()));
}

任何建议将不胜感激,谢谢:)

【问题讨论】:

    标签: c++ templates reflection protocol-buffers


    【解决方案1】:

    正确答案

    这里的正确答案是,您应该使用Reflection::GetRepeatedMessage(),而不是使用Reflection::GetRepeatedMessage(),为重复字段的每个元素获取一个通用的const Message&amp;。不幸的是,您需要为每个元素调用一次此方法(使用 Reflection::FieldSize() 查找大小)。

    在每个Message 上,您可以使用Message::GetDescriptor(),查找名为test_field 的字段,然后查找Message::GetReflection() 并使用它来读取该字段的值。作为优化,您可以放心地假设同一重复字段中的所有消息具有相同的DescriptorReflection 对象,因此您只需获取这些对象并为整个数组查找一次FieldDescriptor

    类似的东西(未测试):

    int size = pReflection->FieldSize(*BATTLE_DATA, pMessageField);
    Reflection* pInnerReflection = NULL;
    FieldDescriptor* pTestFieldDesc = NULL;
    for (int i = 0; i < size; i++) {
      const Message& msg = pReflection->GetRepeatedMessage(
          *BATTLE_DATA, pMessageField, i);
      if (pInnerReflection == NULL) {
        pInnerReflection = msg.GetReflection();
        pTestFieldDesc = msg.GetDescriptor()
            ->FindFieldByName("test_field");
      }
      int testField = pInnerReflection->GetInt32(msg, pTestFieldDesc);
      // ...
    }
    

    一个有趣但糟糕的答案

    第二个解决方案的性能会稍微好一些,看起来更漂亮一些。不幸的是,技术上是未定义的行为。它适用于所有编译器,但除非您遇到性能问题,否则您可能不应该这样做。我把它放在这里是为了好玩,因为无论如何你都可能会弄明白,所以很高兴知道它为什么不好。

    您可以调用GetRepeatedPtrField&lt;google::protobuf::Message&gt;(...) 来获取通用Message 对象的RepeatedPtrField。然后您可以像第一个解决方案一样获取描述符和反射,但您不再需要调用Reflection::GetMessage(虚拟调用)来读取每条消息。 (不过,您仍然需要调用反射来读取 test_field。)

    这在技术上是未定义的行为,因为它假设了一些事情:

    • RepeatedPtrField&lt;T&gt; 对所有T 具有相同的布局。这实际上是正确的,因为 RepeatedPtrField&lt;T&gt; 扩展了 RepeatedPtrFieldBase 并且没有添加任何新字段,但 C++ 标准并不要求这种情况。
    • 将特定消息类型指针(如AInfo*)向上转换为Message* 不会更改指针的位。在实践中,只要没有多重继承,所有编译器都是如此,并且 Protobufs 不使用多重继承(Google C++ 风格指南甚至禁止它)。
    • 编译器不会根据严格的别名规则进行优化,从而导致对您的RepeatedPtrField&lt;Message&gt; 的访问重新排序,而不是对程序中其他地方发生的RepeatedPtrField&lt;AInfo&gt;(或任何类型)的其他访问进行重新排序。在实践中,无法想象任何编译器会为此用例实际执行此操作,但 C++ 在技术上允许这样做。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-05-15
      • 1970-01-01
      • 1970-01-01
      • 2011-09-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多