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

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

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

アリーナアロケーションを使用する理由

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

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

これらの利点を得るには、オブジェクトのライフサイクルを認識し、アリーナを使用する適切な粒度を見つける必要があります(サーバーの場合、これはリクエストごとであることがよくあります)。アリーナアロケーションを最大限に活用する方法の詳細については、使用パターンとベストプラクティスをご覧ください。

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

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

はじめに

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

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

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

ほとんどの場合、コードの残りの部分は、アリーナアロケーションを使用していない場合と同じになります。

アリーナ 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はトリビアルなコンストラクタを持っている必要があります。配列がアリーナ上に作成されるときにコンストラクタは呼び出されません。

「Owned list」メソッド

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

  • 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(number-of-declared-fields)です)。ただし、ソースメッセージがアリーナ上にある場合、基になるデータのディープコピーを実行します。どちらの場合も、ソースメッセージは有効ですが、不特定の状態になります。
  • Message& operator=(Message&& other): 両方のメッセージがアリーナ上にない場合、または同じアリーナ上にある場合、ムーブ代入演算子は、コピーやヒープ割り当てを行わずに、あるメッセージから別のメッセージにすべてのフィールドを効率的に移動します(この操作の時間計算量はO(number-of-declared-fields)です)。ただし、メッセージの1つだけがアリーナ上にある場合、またはメッセージが異なるアリーナ上にある場合、基になるデータのディープコピーを実行します。どちらの場合も、ソースメッセージは有効ですが、不特定の状態になります。
  • void Swap(Message* other): スワップされる両方のメッセージがアリーナ上にない場合、または同じアリーナ上にある場合、Swap()はアリーナアロケーションが有効になっていない場合と同じように動作します。メッセージオブジェクトの内容を効率的にスワップします。ほぼ排他的に安価なポインタスワップを通じて、コピーを回避します。ただし、メッセージの1つだけがアリーナ上にある場合、またはメッセージが異なるアリーナ上にある場合、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からのものである場合、オブジェクトはヒープ割り当てされますが、親メッセージのアリーナによって「所有」されます。これは、アリーナが破棄されると、オブジェクトがアリーナ自体上のオブジェクトとともに解放されることを意味します。

これらのフィールド定義のいずれかについて

optional Bar foo = 1;
required 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 patternsを参照してください。
  • Bar* unsafe_arena_release_foo(): release_foo()と同様ですが、すべての所有権チェックをスキップします。安全な使用方法の詳細については、allocated/release patternsを参照してください。

String フィールド

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

Repeated フィールド

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

数値 Repeated フィールド

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

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

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

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

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

  • void Swap(RepeatedPtrField* other): 各 repeated フィールドオブジェクトのアリーナポインタをチェックし、一方が非 NULL (アリーナ上のコンテンツ) であり、もう一方が NULL (ヒープ上のコンテンツ) である場合、または両方が非 NULL であるが異なる値を持っている場合、スワップが発生する前に基になる配列とそのポイント先のオブジェクトがコピーされます。これは、スワップ後、各 repeated フィールドオブジェクトが、必要に応じて、独自のアリーナまたはヒープ上の配列を保持することを意味します。

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

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

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

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

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

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

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

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

String の Repeated フィールド

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

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

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

意図しないコピー

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

Set Allocated/Add Allocated/Release

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

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

このようなコピーを回避するために、コピーが決して実行されない、これらのメソッドの対応する「unsafe arena」バージョンを追加しました。それぞれ、単数および repeated フィールド用の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());

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

Swap

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

syntax = "proto2";
import "nested_message.proto";

package feature_package;

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

syntax = "proto2";

package feature_package;

// NEXT Tag to use: 2
message NestedMessage {
  optional 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("Proto2 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. 現在、string フィールドは、コンテナメッセージがアリーナ上にある場合でも、そのデータをヒープに格納します。不明なフィールドもヒープ割り当てされます。 ↩︎

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