ProtoBuf: как повторно использовать FieldDescriptor для новых сообщений? Как эффективно получить значения полей из динамических c сообщений? - PullRequest
0 голосов
/ 06 февраля 2020

В настоящее время я работаю над проектом, в котором я хочу использовать буфер протокола Google (C ++) для сериализации и десериализации данных. Приложение имеет следующие требования:

  1. Одно из требований - НЕ использовать стандартный подход генерации предварительно скомпилированных классов C ++ из файлов .proto (используя proto c). Вместо этого я использую google::protobuf::compiler::Importer для импорта моих .proto файлов во время выполнения, а затем динамически создаю google::protobuf::Message с использованием google::protobuf::DynamicMessageFactory. При таком подходе я уже могу сериализовать / десериализовать байтовые массивы / сообщения без предварительной генерации классов C ++.
  2. Производительность: я ожидаю новых байтовых потоков примерно каждые 50 мс, поэтому анализирую байтовый массив и считываю значения из динамически Созданное сообщение должно быть достаточно эффективным. На этапе разбора я просто использую стандартный метод ParseFromArray(…), чтобы получить мое сообщение. Пока я не вижу необходимости оптимизировать этот шаг. Вместо этого я сейчас ищу способ получить значения более эффективно.

Я знаю, что 1. и 2. противоречат друг другу, потому что использование DynamicMessageFactory, вероятно, дороже, чем генерация предварительно скомпилированные классы C ++, но, к сожалению, 1. это сложное требование и не может быть изменено.

Для получения значений моего проанализированного сообщения в настоящее время я использую google::protobuf::Reflection и google::protobuf::Descriptor для итерации своего сообщения. пока соответствующий google::protobuf::FieldDescriptor не будет найден. Поскольку итерация по сообщению довольно неэффективна, особенно когда ожидается, что сообщение будет содержать ~ 1000 полей, я подумал о том, чтобы кэшировать найденные FieldDescriptor на карте, а затем повторно использовать кэшированные FieldDescriptor для других сообщений, не просматривая каждое из них. сообщение снова (так как все мои сообщения имеют одинаковую структуру в любом случае). Этот подход был предложен здесь . К сожалению, мне не удалось заставить его работать (см. Мой упрощенный пример кода ниже). Ребята, вы можете мне помочь? Заранее благодарю за любые предложения.

int main()
{
  MyUtil util; // My protobuf utility class

  int size;
  uint8_t* data = util.GenerateByteArray(&size); // Generate sample byte array

  Message* message1 = util.ParseFromArray(data, size);
  util.SetDouble(message1, "position.x", 1.111);

  Message* message2 = util.ParseFromArray(data, size);
  util.SetDouble(message2, "position.x", 2.222);

  cout << util.GetDouble(message1, "position.x") << endl;
  cout << util.GetDouble(message2, "position.x") << endl;

  // This works as expected if my GetDouble method iterates through the whole message for every new message.
  // But if I try to cache my FieldDescriptors I get the wrong output (see below).

  return 0;
}

class MyUtil
{
private:
  // The cached descriptors
  const Message* mTempMessage = nullptr;
  const FieldDescriptor* mTempField = nullptr;

public:
  MyUtil() { /*Initializing and importing .proto files*/ }

  // ... Some other methods

  double GetDouble(const Message* message, const string& path)
  {
    if (mTempField)
    {
      // Problem: This doesn't work if I only pass the actual 'message2' so I attempted to also cache mTempMessage.
      // But mTempMessage will always refer to the original message1 from which mTempField was retrieved.
      // This is why util.GetDouble(message2, "position.x") will always only return the value of message1.
      // So I know why my output is wrong but I don't know how the correct solution should look like.
      return mTempMessage->GetReflection()->GetDouble(*mTempMessage, mTempField);
    }

    vector<string> fieldNames;
    boost::split(fieldNames, path, boost::is_any_of(".")); // "position.x" => ["position", "x"]

    vector<string>::iterator iterator = fieldNames.begin();
    vector<string>::iterator last = fieldNames.end() - 1;

    return GetDouble(message, &iterator, &last);
  }

  double GetDouble(const Message* message, vector<string>::iterator* pathIterator, vector<string>::iterator* pathLast)
  {
    // Get FieldDescriptor using message->GetDescriptor()->FindFieldByName(...)
    const FieldDescriptor* field = GetField(message, **pathIterator);

    if (*pathIterator != *pathLast)
    {
      // Field "position": Is another message, so call recursively for next field
      (*pathIterator)++;

      // Get the inner "position" message using Reflection
      const Message* messageField = GetMessageField(message, field);

      return GetDouble(messageField, pathIterator, pathLast);
    }
    else if (field->type() == FieldDescriptor::TYPE_DOUBLE)
    {
      // Field "x": Actual value can be retrieved

      // Caching the found FieldDescriptor (and also the current "position" message)
      mTempMessage = message;
      mTempField = field;

      return message->GetReflection()->GetDouble(*message, field);
    }
    else
    {
      // Return some invalid value
    }
  }
};
...