protobufの任意のMessageをdeserializeする

# はじめに

protobufの任意の`Message`をdeserializeする方法を調べたので、備忘メモを残しておきます。

誤りやより良い方法などありましたら、ぜひご指摘ください。

以下の環境で確認しています。

```shell
$ lsb_release -d
Description:    Ubuntu 18.04.5 LTS
$ uname -r
5.5.8
$ uname -m
x86_64
$ protoc --version
libprotoc 3.14.0
```

# protobufの任意の`Message`をdeserializeする

protobufでdeserializeするためには、
「ユーザ定義`Message`型」のインスタンスの`ParseFromXXX()`メソッドを呼び出す必要があります。


イベント発生時にdeserializeする場合などは
この「ユーザ定義`Message`型」を動的に決定する必要があります。

C++では型情報を取り回す方法として`type_info`型が用意されていますが、
残念ながら`type_info`型からインスタンスを生成することはできないので今回は使えません。

というわけで、protobufで動的に「ユーザ定義`Message`型」を決定し、deserializeするためには
下記３つのアプローチが考えられます。


- 方式0. 実インスタンス方式
  - Parseする可能性のある`Message`の実インスタンスを持っておき、必要なタイミングで`ParseFromXXX()`を呼び出す

- 方式1. デフォルトインスタンス方式
  - Parseする可能性のある`Message`のデフォルトインスタンスへの参照を持っておき、
    必要なタイミングでインスタンスを生成してから`ParseFromXXX()`を呼び出す

- 方式2. `Descriptor`方式
  - Parseする可能性のある`Message`の型情報(`Descriptor`)へのポインタを持っておき、
    必要なタイミングでインスタンスを生成してから`ParseFromXXX()`を呼び出す

方式0はメモリ効率性からあまり現実的ではなさそうなのと、
仮に採用できたとして実装もそのままなので今回は省略します。

先に結論を言えば、選べるなら**方式1**の方が簡単ですし、効率が良い分望ましいです。
すでに`Descriptor`で管理していて変更が難しい場合には、方式2を選択することになると思います。

## 方式1. デフォルトインスタンス方式

デフォルトインスタンス方式の場合の処理は下記のようになります。

1. immutableなデフォルトインスタンスから、mutableな`Message`インスタンスを生成する
2. 生成した`Message`インスタンスの`ParseFromXXX`を呼び出し、deserializeする
3. 「ユーザ定義`Message`型」にdown castして参照する

実装は下記のようになります。

`Message::New`で取得したインスタンスの解放責任は呼び出し元にあるので、ここでは`unique_ptr`で受けています。

```c++
static std::unique_ptr<Message> LoadMessageBasedOnMessage(
    const Message& message) {
  // 1. Message::Newで、mutableなMessageを生成する
  //    得たMessageは呼び出し元が解放しなければならないので、unique_ptrで受ける
  auto new_message = std::unique_ptr<Message>(message.New());
  if (new_message == nullptr) {
    return nullptr;
  }

  // 2. Message::ParseFromXXXで、Messageをdeserialize
  new_message->ParseFromString(serialized_message);

  return new_message;
}
```

呼び出し元は以下のようなイメージです。

protobufでは、C++のRTTIが使えないときでもdynamic castできるように
[`DynamicCastToGenerated`](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.message#DynamicCastToGenerated.details)というAPIが用意されています。

```c++
    std::unique_ptr<Message> message =
        LoadMessageBasedOnMessage(SampleMessage::default_instance());

    // 3. ユーザ定義Message型にdown cast
    SampleMessage* sample_message =
        DynamicCastToGenerated<SampleMessage>(message.get());
```



## 方式2. Descriptor方式

Descriptor方式の場合の処理は下記のようになります。

0. `DynamicMessageFactory`を生成する
1. `DynamicMessageFactory::GetPrototype`で、Descriptorからconst Messageを取得
2. `Message::New`で、mutableな`Message`インスタンスを生成する
3. 生成した`Message`インスタンスの`ParseFromXXX`を呼び出し、deserializeする
4. 「ユーザ定義`Message`型」にコピーして参照する

`Descriptor`から`Message`を生成するためには、
`DynamicMessageFactory::GetPrototype`を使います。

ただし、注意点が2つあります。

1つめは、`GetPrototype`で生成した`Message`および、そこから`New`した`Message`は、
**`DynamicMessageFactory`が破棄されたあとは参照してはいけません**。

2つめは、`GetPrototype`で生成した`Message`は、`DynamicMessage`型にユーザ定義`Message`型と同じ`Reflection`をもたせたものです。
つまり、振る舞いとしてはユーザ定義`Message`型と同じですが、
型としては異なるので**ユーザ定義`Message`型へのdown castはできません**。
厳密にユーザ定義`Message`型として使うためには、
ユーザ定義`Message`型のインスタンスに`CopyFrom`する必要があります。


これらを加味し、方式1と同等の処理を行った実装が下記です。
返す`Message`以上に生存期間の長い`DynamicMessageFactory`が必要なので、引数で渡してもらっています。

```c++
static std::unique_ptr<Message> LoadMessageBasedOnDescriptor(
    DynamicMessageFactory* factory, const Descriptor* descriptor) {
  if ((factory == nullptr) || (descriptor == nullptr)) {
    return nullptr;
  }

  // 1. DynamicMessageFactory::GetPrototypeで、Descriptorからconst Messageを取得
  const Message* prototype = factory->GetPrototype(descriptor);
  if (prototype == nullptr) {
    return nullptr;
  }

  // 2. Message::Newで、mutableなMessageを生成する
  //    得たMessageは呼び出し元が解放しなければならないので、unique_ptrで受ける
  auto message = std::unique_ptr<Message>(prototype->New());
  if (message == nullptr) {
    return nullptr;
  }

  // 3. Message::ParseFromXXXで、Messageをdeserialize
  message->ParseFromString(serialized_message);

  return message;
}
```

呼び出し元は以下のような感じです。

down castができないので、`Message::CopyFrom`しています。

`Reflection`経由でしかプロパティにアクセスせず、
かつ`DynamicMessageFactrory`の生存期間を`Message`より長くできるのであれば、
4.のコピーを避けることもできると思います。

```c++
    {
      // 0. DynamicMessageFactoryを生成
      DynamicMessageFactory factory;

      std::unique_ptr<Message> message =
          LoadMessageBasedOnDescriptor(&factory, SampleMessage::descriptor());

      // 4. ユーザ定義Message型にコピー
      sample_message.CopyFrom(*message);

      // 5. ここでDynamicMessageFactory解放
      //    以降、factoryから生成したmessageは参照してはいけない
      //    sample_messageは引き続き使用できる
    }
```

# 性能比較

それぞれ1,000,000回実行した時間の平均を比較すると、
方式2の方が約2倍時間がかかっています。

| 方式   |  時間[ns]          |
| ----- | ---------- |
| 方式1 | 1,927 |
| 方式2 | 3,716 |

コピーが1回→2回に増えているので、およそ想定通りです。

計測環境は以下のとおりです。

```
$ grep 'model name' /proc/cpuinfo | uniq -c
      8 model name  : Intel(R) Core(TM) i5-8259U CPU @ 2.30GHz
$ grep MemTotal /proc/meminfo 
MemTotal:       32752704 kB
$ uname -r
5.5.8
$ lsb_release -d
Description:    Ubuntu 18.04.5 LTS
```

# 終わりに

今回、[`DynamicMessageFactory::GetPrototype`](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.dynamic_message#DynamicMessageFactory.GetPrototype.details)のドキュメントを読んでいて、
以下の文言を見つけたのが調べるきっかけでした。
`New`したら独立したインスタンスが返ってくると思い込んでいたので、驚きました。

> Also, any objects created by calling the prototype's New() method share some data with the prototype, so these must be destroyed before the DynamicMessageFactory is destroyed.

`protobuf`ではC++のような静的型付け言語でもかなり動的に型を処理できるよう工夫してくれています。
一方で効率も求められるので、どうしてもトリッキーな処理や直感に背く振る舞いも出てきてしまうのだと思います。

あらためて、`protobuf`で動的に型を処理する場合は慎重にならないとと感じました。

蛇足ですが、今回`DynamicMessageFactory`と`DynamicMessage`の関係を確認するのに
`valgrind --leak-check=full --show-leak-kinds=all`がとても役に立ってくれました。

# 実験コード

今回の記事で使った実験コードは、以下に置いています。

[takeoverjp/protobuf-sandbox](https://github.com/takeoverjp/protobuf-sandbox/blob/master/load_message.cc)

# 参考
[Message::DynamicCastToGenerated](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.message#DynamicCastToGenerated.details)
[DynamicMessageFactory](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.dynamic_message)