C++アリーナアロケーションガイド

アリーナアロケーションはC++専用の機能で、プロトコルバッファを扱う際のメモリ使用量を最適化し、パフォーマンスを向上させるのに役立ちます。

このページでは、アリーナアロケーションが有効になっている場合に、プロトコルバッファコンパイラがC++生成コードガイドに記載されているコードに加えて、どのようなC++コードを生成するのかを正確に説明します。言語ガイドC++生成コードガイドの資料に精通していることを前提としています。

なぜアリーナアロケーションを使用するのか?

プロトコルバッファのコードで費やされるCPU時間の大部分は、メモリの割り当てと解放です。デフォルトでは、プロトコルバッファは各メッセージオブジェクト、そのサブオブジェクト、および文字列などのいくつかのフィールドタイプに対してヒープ割り当てを実行します。これらの割り当ては、メッセージの解析時やメモリ内で新しいメッセージを構築する際に一括で行われ、関連する解放はメッセージとそのサブオブジェクトツリーが解放される際に発生します。

アリーナベースの割り当ては、このパフォーマンスコストを削減するように設計されています。アリーナ割り当てでは、新しいオブジェクトは、アリーナと呼ばれる大きな事前に割り当てられたメモリ領域から割り当てられます。オブジェクトは、アリーナ全体を破棄することで一度にすべて解放できます。理想的には、含まれるオブジェクトのデストラクタを実行せずに(ただし、アリーナは必要に応じて「デストラクタリスト」を維持することもできます)。これにより、オブジェクトの割り当ては単純なポインタインクリメントに減り、デ割り当てはほぼ無料になります。アリーナ割り当ては、キャッシュ効率も向上させます。メッセージが解析されると、連続したメモリに割り当てられる可能性が高くなり、メッセージをトラバースする際にホットキャッシュラインにヒットする可能性が高くなります。

これらの利点を得るには、オブジェクトのライフタイムを意識し、アリーナを使用するのに適した粒度を見つける必要があります(サーバーの場合、これは通常リクエストごとです)。アリーナ割り当てを最大限に活用する方法については、「使用パターンとベストプラクティス」で詳しく説明されています。

この表は、アリーナを使用することによる一般的なパフォーマンス上の利点と欠点をまとめたものです。

操作ヒープ割り当てプロトメッセージアリーナ割り当てプロトメッセージ
メッセージ割り当て平均的に遅い平均的に速い
メッセージ破棄平均的に遅い平均的に速い
メッセージの移動常に移動(コストはシャローコピーと同等)時にはディープコピー

はじめに

プロトコルバッファコンパイラは、以下の例で使用されているように、ファイル内のメッセージのアリーナ割り当て用のコードを生成します。

#include <google/protobuf/arena.h>
{
  google::protobuf::Arena arena;
  MyMessage* message = google::protobuf::Arena::Create<MyMessage>(&arena);
  // ...
}

Create()によって作成されたメッセージオブジェクトは、arenaが存在する限り存在し、返されたメッセージポインタをdeleteしてはいけません。メッセージオブジェクトの内部ストレージ(いくつかの例外を除いて1)とサブメッセージ(たとえば、MyMessage内の繰り返しフィールドのサブメッセージ)もアリーナに割り当てられます。

ほとんどの場合、コードの残りの部分はアリーナ割り当てを使用しない場合と同じです。

以降のセクションではアリーナAPIをさらに詳しく見ていき、ドキュメントの最後にはより詳細ながあります。

ArenaクラスAPI

google::protobuf::Arenaクラスを使用して、アリーナ上にメッセージオブジェクトを作成します。このクラスは、以下の公開メソッドを実装しています。

コンストラクター

  • Arena(): 平均的なユースケース向けに調整された、デフォルトのパラメータを持つ新しいアリーナを作成します。
  • Arena(const ArenaOptions& options): 指定された割り当てオプションを使用する新しいアリーナを作成します。ArenaOptionsで利用可能なオプションには、システムアロケータに頼る前に、ユーザーが提供する初期メモリブロックを割り当てに使用する機能、メモリブロックの初期および最大要求サイズを制御する機能、およびカスタムブロック割り当て/解放関数ポインタを渡してブロックの上にフリーリストなどを構築する機能が含まれます。

割り当てメソッド

  • template<typename T> static T* Create(Arena* arena) または template<typename T> static T* Create(Arena* arena, args...)

    • Tが完全に互換性がある2場合、このメソッドは型Tの新しいプロトコルバッファオブジェクトとそのサブオブジェクトをアリーナ上に作成します。

      arenaがNULLでない場合、返されたオブジェクトはアリーナに割り当てられ、その内部ストレージとサブタイプ(もしあれば)は同じアリーナに割り当てられ、そのライフタイムはアリーナと同じです。オブジェクトを手動で削除/解放してはなりません。アリーナがライフタイムの目的でオブジェクトを所有します。

      arenaがNULLの場合、返されたオブジェクトはヒープに割り当てられ、呼び出し元が返されたオブジェクトを所有します。

    • Tがユーザー定義型の場合、このメソッドでオブジェクトを作成できますが、サブオブジェクトはアリーナに作成できません。たとえば、次のようなC++クラスがあるとします。

      class MyCustomClass {
          MyCustomClass(int arg1, int arg2);
          // ...
      };
      

      ...アリーナ上にそのインスタンスをこのように作成できます。

      void func() {
          // ...
          google::protobuf::Arena arena;
          MyCustomClass* c = google::protobuf::Arena::Create<MyCustomClass>(&arena, constructor_arg1, constructor_arg2);
          // ...
      }
      
  • template<typename T> static T* CreateArray(Arena* arena, size_t n): arenaがNULLでない場合、このメソッドは型Tn個の要素のための生ストレージを割り当て、それを返します。アリーナは返されたメモリを所有し、その破棄時に解放します。arenaがNULLの場合、このメソッドはヒープにストレージを割り当て、呼び出し元が所有権を受け取ります。

    Tは単純なコンストラクタを持っていなければなりません。アリーナ上で配列が作成される際、コンストラクタは呼び出されません。

「所有リスト」メソッド

以下のメソッドでは、特定オブジェクトまたはデストラクタがアリーナによって「所有」されることを指定できます。これにより、アリーナ自体が削除されたときにそれらが削除または呼び出されることが保証されます。

  • template<typename T> void Own(T* object): objectをアリーナの所有するヒープオブジェクトのリストに追加します。アリーナが破棄されると、このリストをたどり、各オブジェクトをoperator delete(つまり、システムメモリ割り当て器)を使用して解放します。このメソッドは、オブジェクトのライフタイムがアリーナに関連付けられるべきだが、何らかの理由でオブジェクト自体がアリーナに割り当てられない、またはすでに割り当てられていない場合に役立ちます。
  • template<typename T> void OwnDestructor(T* object): objectのデストラクタを、アリーナが呼び出すデストラクタのリストに追加します。アリーナが破棄されると、このリストをたどり、各デストラクタを順番に呼び出します。オブジェクトの基盤となるメモリを解放しようとはしません。このメソッドは、オブジェクトがアリーナに割り当てられたストレージに埋め込まれているが、そのデストラクタがそれ以外に呼び出されない場合(たとえば、その包含クラスがデストラクタが呼び出されないprotobufメッセージであるため、またはAllocateArray()によって割り当てられたブロックで手動で構築されたため)に役立ちます。

その他のメソッド

  • uint64 SpaceUsed() const: アリーナの総サイズを返します。これは基盤となるブロックのサイズの合計です。このメソッドはスレッドセーフですが、複数のスレッドからの同時割り当てがある場合、このメソッドの戻り値には新しいブロックのサイズが含まれない可能性があります。
  • uint64 Reset(): アリーナのストレージを破棄します。まず登録されているすべてのデストラクタを呼び出し、登録されているすべてのヒープオブジェクトを解放し、その後すべてのアリーナブロックを破棄します。このティアダウン手順は、アリーナのデストラクタが実行される場合と同じですが、このメソッドが返された後もアリーナは新しい割り当てに再利用できます。アリーナによって使用された総サイズを返します。この情報はパフォーマンスを調整するのに役立ちます。
  • template<typename T> Arena* GetArena(): このアリーナへのポインタを返します。直接的にはあまり有用ではありませんが、GetArena()メソッドが存在することを期待するテンプレートインスタンス化でArenaを使用できます。

スレッドセーフティ

google::protobuf::Arenaの割り当てメソッドはスレッドセーフであり、基盤となる実装はマルチスレッド割り当てを高速化するためにかなり工夫されています。Reset()メソッドはスレッドセーフでは**ありません**。アリーナのリセットを実行するスレッドは、まず割り当てを実行している、またはそのアリーナから割り当てられたオブジェクトを使用しているすべてのスレッドと同期する必要があります。

生成されたメッセージクラス

アリーナアロケーションを有効にすると、以下のメッセージクラスメンバーが変更または追加されます。

メッセージクラスメソッド

  • Message(Message&& other): ソースメッセージがアリーナ上にない場合、ムーブコンストラクタはコピーやヒープ割り当てを行わずに、すべてのフィールドをあるメッセージから別のメッセージに効率的に移動します(この操作の時間計算量はO(宣言されたフィールドの数)です)。ただし、ソースメッセージがアリーナ上にある場合、基になるデータのディープコピーを実行します。どちらの場合も、ソースメッセージは有効ですが未指定の状態のままになります。
  • Message& operator=(Message&& other): 両方のメッセージがアリーナ上にない場合、または同じアリーナ上にある場合、ムーブ代入演算子は、コピーやヒープ割り当てを行わずに、すべてのフィールドをあるメッセージから別のメッセージに効率的に移動します(この操作の時間計算量はO(宣言されたフィールドの数)です)。ただし、いずれか一方のメッセージのみがアリーナ上にある場合、またはメッセージが異なるアリーナ上にある場合、基になるデータのディープコピーを実行します。どちらの場合も、ソースメッセージは有効ですが未指定の状態のままになります。
  • void Swap(Message* other): スワップされる両方のメッセージがアリーナ上にないか、または同じアリーナ上にある場合、Swap()はアリーナ割り当てが有効でない場合と同様に動作します。メッセージオブジェクトの内容を効率的にスワップし、ほとんどが安価なポインタスワップによってコピーを回避します。しかし、メッセージのいずれか一方のみがアリーナ上にある場合、またはメッセージが異なるアリーナ上にある場合、Swap()は基盤となるデータのディープコピーを実行します。この新しい動作が必要なのは、そうでなければスワップされたサブオブジェクトのライフタイムが異なる可能性があり、use-after-freeバグにつながる可能性があるためです。
  • Message* New(Arena* arena): 標準のNew()メソッドに対する代替オーバーライド。この型​​の新しいメッセージオブジェクトを、指定されたアリーナ上に作成できます。呼び出された具象メッセージ型がアリーナ割り当てを有効にして生成されている場合、そのセマンティクスはArena::Create<T>(arena)と同じです。メッセージ型がアリーナ割り当てを有効にして生成されていない場合、arenaがNULLでなければ、通常の割り当ての後にarena->Own(message)が行われるのと同等です。
  • Arena* GetArena(): このメッセージオブジェクトが割り当てられたアリーナ(もしあれば)へのポインタを返します。
  • void UnsafeArenaSwap(Message* other): Swap()と同じですが、両方のオブジェクトが同じアリーナ上にある(またはまったくアリーナ上にない)と仮定し、常にこの操作の効率的なポインタスワップ実装を使用します。このメソッドを使用すると、Swap()とは異なり、スワップを実行する前にどのメッセージがどのアリーナにあるかを確認する必要がないため、パフォーマンスが向上します。Unsafeという接頭辞が示すように、スワップしたいメッセージが異なるアリーナにないことが確実な場合にのみこのメソッドを使用してください。そうでない場合、このメソッドは予測不能な結果をもたらす可能性があります。

埋め込みメッセージフィールド

アリーナ上にメッセージオブジェクトを割り当てると、その埋め込みメッセージフィールドオブジェクト(サブメッセージ)も自動的にアリーナによって所有されます。これらのメッセージオブジェクトがどのように割り当てられるかは、それらがどこで定義されているかによって異なります。

  • メッセージ型もアリーナ割り当てが有効な`.proto`ファイルで定義されている場合、オブジェクトはアリーナに直接割り当てられます。
  • メッセージ型がアリーナ割り当てが有効でない別の`.proto`からのものである場合、オブジェクトはヒープに割り当てられますが、親メッセージのアリーナによって「所有」されます。これは、アリーナが破棄されると、オブジェクト自体のアリーナ上のオブジェクトと一緒に解放されることを意味します。

フィールド定義の場合

Bar foo = 1;

アリーナ割り当てが有効になっている場合、以下のメソッドが追加されるか、特別な動作をします。それ以外の場合、アクセサメソッドはデフォルトの動作を使用します。

  • Bar* mutable_foo(): サブメッセージインスタンスへのミュータブルポインタを返します。親オブジェクトがアリーナ上にある場合、返されるオブジェクトもアリーナ上にあります。
  • void set_allocated_foo(Bar* bar): 新しいオブジェクトを受け取り、それをフィールドの新しい値として採用します。アリーナサポートは、オブジェクトがアリーナ/アリーナまたはアリーナ/ヒープの境界を越えるときに適切な所有権を維持するために、追加のコピーセマンティクスを追加します。
    • 親オブジェクトがヒープ上にあり、barがヒープ上にある場合、または親とメッセージが同じアリーナ上にある場合、このメソッドの動作は変更されません。
    • 親がアリーナ上にあり、barがヒープ上にある場合、親メッセージはarena->Own()を使用してbarをアリーナの所有権リストに追加します。
    • 親がアリーナ上にあり、barが別のアリーナ上にある場合、このメソッドはメッセージのコピーを作成し、そのコピーを新しいフィールド値として使用します。
  • Bar* release_foo(): フィールドの既存のサブメッセージインスタンスが設定されている場合はそれを返し、設定されていない場合はNULLポインタを返します。このインスタンスの所有権を呼び出し元に解放し、親メッセージのフィールドをクリアします。アリーナのサポートは、返されるオブジェクトが常にヒープに割り当てられているという契約を維持するために、追加のコピーセマンティクスを追加します。
    • 親メッセージがアリーナ上にある場合、このメソッドはサブメッセージのコピーをヒープ上に作成し、フィールド値をクリアして、そのコピーを返します。
    • 親メッセージがヒープ上にある場合、メソッドの動作は変更されません。
  • void unsafe_arena_set_allocated_foo(Bar* bar): set_allocated_fooと同じですが、親とサブメッセージの両方が同じアリーナ上にあることを前提としています。このバージョンのメソッドを使用すると、メッセージが特定のアリーナ上にあるかヒープ上にあるかを確認する必要がないため、パフォーマンスが向上します。安全な使用方法の詳細については、allocated/releaseパターンを参照してください。
  • Bar* unsafe_arena_release_foo(): release_foo()に似ていますが、すべての所有権チェックをスキップします。安全な使用方法の詳細については、allocated/releaseパターンを参照してください。

文字列フィールド

文字列フィールドは、親メッセージがアリーナ上にある場合でも、データをヒープに格納します。このため、文字列アクセサメソッドは、アリーナ割り当てが有効になっている場合でも、デフォルトの動作を使用します。

繰り返しフィールド

繰り返しフィールドは、コンテナメッセージがアリーナ割り当てされている場合、その内部配列ストレージをアリーナに割り当て、また、これらの要素がポインタによって保持される個別のオブジェクト(メッセージまたは文字列)である場合、それらの要素もアリーナに割り当てます。メッセージクラスレベルでは、繰り返しフィールドに対して生成されるメソッドは変更されません。ただし、アリーナサポートが有効になっている場合、アクセサによって返されるRepeatedFieldおよびRepeatedPtrFieldオブジェクトには、新しいメソッドと変更されたセマンティクスがあります。

繰り返し数値フィールド

アリーナ割り当てが有効になっている場合、プリミティブ型を含むRepeatedFieldオブジェクトには、次の新しい/変更されたメソッドがあります。

  • void UnsafeArenaSwap(RepeatedField* other): この繰り返しフィールドとotherが同じアリーナ上にあることを検証せずに、RepeatedFieldの内容をスワップします。そうでない場合、2つの繰り返しフィールドオブジェクトは同等のライフタイムを持つアリーナ上に存在する必要があります。一方がアリーナ上にあり、もう一方がヒープ上にある場合はチェックされ、許可されません。
  • void Swap(RepeatedField* other): 各繰り返しフィールドオブジェクトのアリーナをチェックし、一方がアリーナ上にあり、もう一方がヒープ上にある場合、または両方ともアリーナ上にあるが異なるアリーナにある場合、スワップが発生する前に基になる配列がコピーされます。これは、スワップ後、各繰り返しフィールドオブジェクトが、適切に自身のアリーナまたはヒープ上の配列を保持することを意味します。

リピート埋め込みメッセージフィールド

アリーナ割り当てが有効になっている場合、メッセージを含むRepeatedPtrFieldオブジェクトには次の新しい/変更されたメソッドがあります。

  • void UnsafeArenaSwap(RepeatedPtrField* other): この繰り返しフィールドとotherが同じアリーナポインタを持つことを検証せずに、RepeatedPtrFieldの内容をスワップします。そうでない場合、2つの繰り返しフィールドオブジェクトは同等のライフタイムを持つアリーナポインタを持っていなければなりません。一方がNULLでないアリーナポインタを持ち、もう一方がNULLアリーナポインタを持つ場合はチェックされ、許可されません。

  • void Swap(RepeatedPtrField* other): 各繰り返しフィールドオブジェクトのアリーナポインタをチェックし、一方がNULLでない(内容がアリーナ上にある)のに対し、もう一方がNULL(内容がヒープ上にある)の場合、または両方ともNULLでないが異なる値を持つ場合、スワップが発生する前に基になる配列とその指すオブジェクトがコピーされます。これは、スワップ後、各繰り返しフィールドオブジェクトが、適切に自身のアリーナ上またはヒープ上の配列を保持することを意味します。

  • void AddAllocated(SubMessageType* value): 提供されたメッセージオブジェクトが、繰り返しフィールドのアリーナポインタと同じアリーナ上にあることを確認します。

    • ソースとデスティネーションの両方がアリーナに割り当てられ、同じアリーナ上にある場合:オブジェクトポインタは直接基になる配列に追加されます。
    • ソースとデスティネーションの両方がアリーナに割り当てられ、異なるアリーナ上にある場合:コピーが作成され、元がヒープに割り当てられていた場合は解放され、コピーが配列に配置されます。
    • ソースがヒープに割り当てられ、デスティネーションがアリーナに割り当てられている場合:コピーは作成されません。
    • ソースがアリーナに割り当てられ、デスティネーションがヒープに割り当てられている場合:コピーが作成され、配列に配置されます。
    • ソースとデスティネーションの両方がヒープに割り当てられている場合:オブジェクトポインタは直接基になる配列に追加されます。

    これにより、繰り返しフィールドによって指されるすべてのオブジェクトが、繰り返しフィールドのアリーナポインタによって示されるものと同じ所有権ドメイン(ヒープまたは特定のアリーナ)にあるという不変条件が維持されます。

  • SubMessageType* ReleaseLast(): 繰り返しフィールドの最後のメッセージと同等のヒープ割り当てメッセージを返し、繰り返しフィールドから削除します。繰り返しフィールド自体がNULLアリーナポインタを持つ場合(したがって、その指すメッセージはすべてヒープ割り当てされている)、このメソッドは単純に元のオブジェクトへのポインタを返します。そうでない場合、繰り返しフィールドがNULLでないアリーナポインタを持つ場合、このメソッドはヒープ割り当てされたコピーを作成し、そのコピーを返します。どちらの場合も、呼び出し元はヒープ割り当てされたオブジェクトの所有権を受け取り、オブジェクトを削除する責任があります。

  • void UnsafeArenaAddAllocated(SubMessageType* value): AddAllocated()に似ていますが、ヒープ/アリーナチェックやメッセージコピーは実行しません。提供されたポインタをこの繰り返しフィールドの内部ポインタ配列に直接追加します。安全な使用方法の詳細については、allocated/releaseパターンを参照してください。

  • SubMessageType* UnsafeArenaReleaseLast(): ReleaseLast()に似ていますが、繰り返しフィールドがNULLでないアリーナポインタを持っていてもコピーは実行しません。代わりに、オブジェクトへのポインタを繰り返しフィールドにあったとおりに直接返します。安全な使用方法の詳細については、allocated/releaseパターンを参照してください。

  • void ExtractSubrange(int start, int num, SubMessageType** elements): 繰り返しフィールドからインデックスstartから始まるnum個の要素を削除し、elementsがNULLでなければそこに返します。繰り返しフィールドがアリーナ上にある場合、要素が返される際には、まず要素がヒープにコピーされます。どちらの場合も(アリーナがあるかないかに関わらず)、呼び出し元はヒープ上の返されたオブジェクトを所有します。

  • void UnsafeArenaExtractSubrange(int start, int num, SubMessageType** elements): 繰り返しフィールドからインデックスstartから始まるnum個の要素を削除し、elementsがNULLでなければそこに返します。ExtractSubrange()とは異なり、このメソッドは抽出された要素をコピーしません。安全な使用方法の詳細については、allocated/releaseパターンを参照してください。

繰り返し文字列フィールド

文字列の繰り返しフィールドは、基盤となるオブジェクト(つまり文字列)もポインタ参照によって維持するため、メッセージの繰り返しフィールドと同じ新しいメソッドと変更されたセマンティクスを持ちます。

使用パターンとベストプラクティス

アリーナに割り当てられたメッセージを使用する場合、いくつかの使用パターンが意図しないコピーやその他の負のパフォーマンス影響をもたらす可能性があります。以下に、アリーナにコードを適応させる際に変更が必要となる可能性のある一般的なパターンについて注意してください。(API設計では、正しい動作が引き続き発生するように注意を払っていますが、より高性能なソリューションにはいくつかの手直しが必要となる場合があります。)

意図しないコピー

アリーナ割り当てを使用しない場合にオブジェクトコピーを作成しないメソッドのいくつかは、アリーナサポートが有効になっている場合にコピーを作成する可能性があります。これらの不要なコピーは、オブジェクトが適切に割り当てられていることを確認したり、以下で詳しく説明するアリーナ固有のメソッドバージョンを使用したりすることで回避できます。

Set Allocated/Add Allocated/Release

デフォルトでは、release_field()およびset_allocated_field()メソッド(単一メッセージフィールド用)と、ReleaseLast()およびAddAllocated()メソッド(繰り返しメッセージフィールド用)は、ユーザーコードがサブメッセージを直接アタッチおよびデタッチし、データをコピーせずにポインタの所有権を渡すことを可能にします。

しかし、親メッセージがアリーナ上にある場合、これらのメソッドは既存の所有権契約との互換性を維持するために、渡された、または返されたオブジェクトをコピーする必要がある場合があります。より具体的には、所有権を受け取るメソッド(set_allocated_field()AddAllocated())は、親がアリーナ上にあり、新しいサブオブジェクトがアリーナ上にない場合、またはその逆の場合、あるいはそれらが異なるアリーナ上にある場合にデータをコピーする可能性があります。所有権を解放するメソッド(release_field()ReleaseLast())は、親がアリーナ上にある場合にデータをコピーする可能性があります。これは、返されたオブジェクトが契約によりヒープ上になければならないためです。

そのようなコピーを避けるために、これらのメソッドには、コピーが**決して実行されない**対応する「unsafe arena」バージョンが追加されています。これらは、それぞれ単一および繰り返しフィールド用のunsafe_arena_set_allocated_field()unsafe_arena_release_field()UnsafeArenaAddAllocated()、およびUnsafeArenaRelease()です。これらのメソッドは、安全であることが分かっている場合にのみ使用する必要があります。これらのメソッドには2つの一般的なパターンがあります。

  • 同じアリーナの異なる部分間でメッセージツリーを移動する場合。このケースが安全であるためには、メッセージが同じアリーナ上にある必要があります。
  • コピーを避けるために、所有されているメッセージを一時的にツリーに貸し出す場合。unsafeなadd/setメソッドとunsafeなreleaseメソッドを組み合わせることで、どちらのメッセージがどのように所有されているかに関わらず、最も安価な方法で貸し出しを実行できます(このパターンは、メッセージが同じアリーナ、異なるアリーナ、またはまったくアリーナ上にない場合に機能します)。ただし、unsafeなadd/setとその対応するreleaseの間では、借用者はスワップ、移動、クリア、または破棄されてはなりません。貸し出されたメッセージはスワップまたは移動されてはなりません。貸し出されたメッセージは借用者によってクリアまたは解放されてはなりません。また、貸し出されたメッセージは破棄されてはなりません。

これらのメソッドで不要なコピーを回避する方法の例を以下に示します。アリーナ上に以下のメッセージを作成したとします。

Arena* arena = new google::protobuf::Arena();
MyFeatureMessage* arena_message_1 =
  google::protobuf::Arena::Create<MyFeatureMessage>(arena);
arena_message_1->mutable_nested_message()->set_feature_id(11);

MyFeatureMessage* arena_message_2 =
  google::protobuf::Arena::Create<MyFeatureMessage>(arena);

以下のコードは、release_...() APIを非効率的に使用しています。

arena_message_2->set_allocated_nested_message(arena_message_1->release_nested_message());

arena_message_1->release_message(); // returns a copy of the underlying nested_message and deletes underlying pointer

代わりに「unsafe arena」バージョンを使用すると、コピーが回避されます。

arena_message_2->unsafe_arena_set_allocated_nested_message(
   arena_message_1->unsafe_arena_release_nested_message());

これらのメソッドの詳細については、上記の「埋め込みメッセージフィールド」セクションを参照してください。

スワップ

2つのメッセージの内容がSwap()で交換される際、2つのメッセージが異なるアリーナ上にある場合、または一方がアリーナ上にあり、もう一方がヒープ上にある場合、基盤となるサブオブジェクトがコピーされる可能性があります。このコピーを避け、かつ(i)2つのメッセージが同じアリーナ上にあるか、異なるアリーナ上にあるがアリーナの寿命が同等であることを知っている場合、または(ii)2つのメッセージがヒープ上にあることを知っている場合、新しいメソッドUnsafeArenaSwap()を使用できます。このメソッドは、アリーナチェックを実行するオーバーヘッドを回避し、コピーが発生する可能性があった場合でもコピーを回避します。

例えば、以下のコードではSwap()呼び出しでコピーが発生します。

MyFeatureMessage* message_1 =
  google::protobuf::Arena::Create<MyFeatureMessage>(arena);
message_1->mutable_nested_message()->set_feature_id(11);

MyFeatureMessage* message_2 = new MyFeatureMessage;
message_2->mutable_nested_message()->set_feature_id(22);

message_1->Swap(message_2); // Inefficient swap!

このコードでコピーを回避するには、message_2message_1と同じアリーナに割り当てます。

MyFeatureMessage* message_2 =
   google::protobuf::Arena::Create<MyFeatureMessage>(arena);

粒度

ほとんどのアプリケーションサーバーのユースケースでは、「リクエストごとにアリーナ」というモデルがうまく機能することがわかっています。ヒープオーバーヘッドを削減するため(より小さなアリーナをより頻繁に破棄することで)または認識されるスレッド競合の問題を軽減するために、アリーナの使用をさらに細分化したいと思うかもしれません。しかし、よりきめ細かいアリーナの使用は、上記で説明したように意図しないメッセージのコピーにつながる可能性があります。また、マルチスレッドのユースケース向けにArenaの実装を最適化する努力も行っているため、複数のスレッドがリクエストを処理する場合でも、リクエストのライフタイム全体で単一のアリーナが適切であるはずです。

アリーナアロケーションAPIの機能の一部を示す、シンプルで完全な例を以下に示します。

// my_feature.proto
edition = "2023";

import "nested_message.proto";

package feature_package;

// NEXT Tag to use: 4
message MyFeatureMessage {
  string feature_name = 1;
  repeated int32 feature_data = 2;
  NestedMessage nested_message = 3;
};
// nested_message.proto
edition = "2023";

package feature_package;

// NEXT Tag to use: 2
message NestedMessage {
  int32 feature_id = 1;
};

メッセージの構築と解放

#include <google/protobuf/arena.h>

Arena arena;

MyFeatureMessage* arena_message =
   google::protobuf::Arena::Create<MyFeatureMessage>(&arena);

arena_message->set_feature_name("Editions Arena");
arena_message->mutable_feature_data()->Add(2);
arena_message->mutable_feature_data()->Add(4);
arena_message->mutable_nested_message()->set_feature_id(247);

  1. 現在、文字列フィールドは、包含メッセージがアリーナ上にある場合でも、データをヒープに保存します。不明なフィールドもヒープに割り当てられます。↩︎

  2. 「完全に互換性のある」型となるために必要なことは、protobufライブラリの内部にあり、信頼できると仮定すべきではありません。↩︎