Proto3:Arena分配指南


Arena分配是僅C++有的功能,在使用Protocol Buffer時,它可以幫助你優化你的內存使用,提高性能。在.proto文件中啟用Arena分配會在生成的C++代碼中添加處理Arena分配的額外代碼。關於Arena分配API的細節,詳見Arena Allocation Guide

服務

如果.proto文件中包含下面的內容:

option cc_generic_services = true;

之后,Protocol Buffer編譯器會根據在本節中描述的文件中找到的服務定義生成代碼。然而,生成的代碼可能不受歡迎,因為它並沒有綁定特定的RPC系統,因此需要為一個系統定制更多級別的間接代碼。如果你不想生成這樣的代碼,你可以在文件中添加這一行:

option cc_generic_services = false;

如果上面的行都未給定,默認選項未false,因此不推薦使用通用服務。(注意,2.4.0版本之前,默認選項為true。)

基於.proto語言服務定義的RPC系統應該提供插件來生成適合系統的代碼。這些插件可能需要禁用抽象服務,以便它們可以生成自己的同名類。插件是2.3.0版本(2010年1月)新引入的。

下面的章節描述抽象服務啟用時編譯器會生成什么。

接口

給出如下服務定義:

service Foo {
  rpc Bar(FooRequest) returns(FooResponse);
}

編譯器會生成一個名為Foo的類來表示該服務。Foo中包含服務定義中定義的每個方法的虛方法。這種情況下,Bar方法定義如下:

virtual void Bar(RpcController* controller, const FooRequest* request,
                 FooResponse* response, Closure* done);

這些參數等同於Service::CallMethod()的參數,只是method參數是隱含的,而requestresponse指定了它們的確切類型。

這些生成的方法是虛,但不是純虛的。默認的實現知識簡單地調用controller->SetFailed(),並使用一條錯誤消息指示方法未實現,然后調用done回調。在實現你自己的服務時,你必須子類化這個生成的服務並適當的實現它的方法。

Foo子類化Service接口。編譯器會自動實現如下的Service的方法:

  • GetDescriptor:返回服務的ServiceDescriptor
  • CallMethod:根據提供的方法描述符確定要調用哪個方法,並直接調用它,將請求和響應消息對象向下轉換為正確的類型。
  • GetRequestPrototypeGetResponsePrototype:針對給定的方法,返回當前類型的請求/響應的默認實例。

也會生成下列靜態方法:

  • static ServiceDescriptor descriptor():返回類型的描述,其中包括該服務包含那些方法以及它們的輸入輸出類型。

Stub

編譯器還為每個服務接口生成了一個“Stub”實現,由要向實現服務的服務器發送請求的客戶端使用。對於Foo服務,Stub實現被定義未Foo_Stub。就像內嵌消息類型一樣,由於typedef的使用,所以可以使用Foo::Stub來引用Foo_Stub

Foo_Stub是實現了如下方法的Foo的子類:

  • Foo_Stub(RpcChannel* channel):在給定的通道上發送請求的新構造的Stub。
  • Foo_Stub(RpcChannel* channel, ChannelOwnership ownership):在給定的通道上發送請求的新構造的Stub,及通道的所有者。如果ownershipService::STUB_OWNS_CHANNEL,之后在刪除stub對象時也會刪除該通道。
  • RpcChannel* channel):返回傳遞給構造函數的stub通道。

stub還額外實現了作為通道打包器的每個服務方法。調用其中一個方法只是簡單地調用channel->CallMethod()

Protocol Buffer庫並不包含RPC實現。但是,它包括將生成的服務類連接到你所選擇的任意RPC實現所需的所有工具。你只需要提供RpcChannelRpcController的實現。更多信息,詳見service.h

插件

想要擴展c++代碼生成器輸出的代碼,Code generator plugin可以使用給定的插入點名稱插入下列類型的代碼。除非另做說明,否則每個插入點都出現在.pb.cc.pb.h文件中。

  • includes:include命令。
  • namespace_scope:屬於文件包/名稱空間,但不屬於任何特定類的聲明。出現在所有其他名稱空間范圍代碼之后。
  • global_scope:屬於頂級聲明,位於文件級明明空間之外。出現在文件末尾。
  • class_scope:TYPENAME:屬於消息類的成員聲明。TYPENAME是完整的proto名稱,例如package.MessageType。在類中,出現在其它公共聲明之后。只出現在.pb.h文件中。

Arena分配指南

為什么要用Arena分配
開始
Arena類API
生成消息類
使用模式和最佳實踐
示例

Arena分配是僅C++有的功能,在使用Protocol Buffer時,它可以幫助你優化你的內存使用,提高性能。本章節是上一章節的補充,它實際上描述了再arena分配啟用時,編譯器生成了什么。本文默認你已經熟悉了語言指南C++代碼生成指南

為什么要用Arena分配

在Protocol Buffer代碼中,內存的分配與釋放占據了CPU耗時的很大一部分。默認情況下,Protocol Buffer為每個消息對象、它的每個子對象,以及一些字段類型,比如字符串,在堆上進行內存分配。在解析消息和構建新的消息時,這個分配操作會大量發生;當消息及其子對象樹釋放時,會產生相關應的釋放操作。

基於Arena的分配被設計用來減小這一性能開銷。使用arena分配,新對象從一個叫做arena的大內存塊中分配。通過丟棄整個arena,可以一次釋放所有對象,理想情況下不需要運行任何被包含對象的析構函數(雖然arena仍然可以在需要時維護一個析構函數列表)。通過將對象分配減少為一個簡單的指針增量,這使得對象分配變得更快,而且使釋放時幾乎沒有消耗。Arena分配還提供了更高的緩存效率:當消息被解析時,它們更有可能被分配到連續內存中,這使得遍歷消息更有可能到達熱緩存線路。

為了獲得這些好處,你需要了解對象的生命周期,並選擇合適的粒度來使用arena(對於服務器,這通常是針對每個請求的)。在使用模式和最佳實踐中,你可以了解更多。

開始

首先你需要在每個.proto文件中啟用arena分配。那么你需要在你的.proto文件中添加如下option

option cc_enable_arenas = true;

這就告訴編譯器為你的消息使用arena分配生成額外的代碼,使用如下:

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

只要arena存在,通過CreateMessage()創建的消息對象就一直存在,而且你不應該delete返回的消息指針。該消息的所有內部存儲(少數例外\(^1\))以及子類消息(例如,MyMessage中重復字段的子類消息)也都在arena上分配。

在大多數情況下,代碼的其余部分與不使用arena分配是一樣的。

在下一小節中會看到更多的arena API的細節,在文末的示例中你能看到更多使用細節。

1 目前,即使包含的消息在arena中,字符串字段也將其數據存儲在堆中。未知字段也是在堆上分配。

Arena類API

在arena上,你可以使用google::protobuf::Arena類來創建消息對象。該類實現了下列的公共方法。

構造函數

  • Arena():使用默認參數創建一個新的arena,針對一般的使用場景。
  • Arena(const ArenaOptions& options):使用特定的分配選項來創建一個新的arena。在ArenaOptions中,可用的選項能夠使用一個初始的用戶提供的內存塊分配之前采取系統分配程序,控制初始和最大請求大小的內存塊,並允許你通過自定義塊分配和回收函數指針來構建釋放列表和其它頂上的塊。

分配方法

  • template<typename T> static T* CreateMessage(Arena* arena):在arena上創建一個新的消息類型為T的Protocol Buffer對象。消息類型必須是定義在.proto文件中,且文件中有option cc_enable_arenas = true;,否則,將導致編譯錯誤。
    如果arena非空,將返回在arena上的消息對象,它的內部存儲以及子類消息(如果有的化)也都在同一arena上分配,且它的聲明周期與該arena的一樣。該對象不能手動釋放:該arena擁有消息對象的生命周期。
    如果arena為空,返回的消息對象被分配在堆上,調用者擁有該對象的所有權。
  • template<typename T> static T* Create(Arena* arena, args...):與CreateMessage()類似,但允許你在arena上創建任何類的對象,即使Protocol Buffer消息類型沒有option cc_enable_arenas = true;:你可以從不支持的arena的文件中使用Protocol Buffer消息類,或任意的C++類。例如,你有如下的C++類:
class MyCustomClass {
    MyCustomClass(int arg1, int arg2);
    // ...
};

你可以在arena上創建它的實例:

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非空, 該方法將為n個類型為T的元素分配原始存儲並返回它。arena所有這返回的內存並在自身銷毀時釋放它。如果arena為空,該方法在堆上分配存儲且調用者獲得所有權。

T必須有一個簡單的構造函數:當數組在arena上創建時,構造函數並不會被調用。

“所有權列表”方法

下面的方法允許你指定特定對象或析構函數為arena所有,從而確保在arena刪除它自己的時候也刪除它們:

  • template<typename T> void Own(T* object):添加object到arena所有的堆對象列表中。當arena銷毀時,它將遍歷整個列表並使用刪除操作釋放每個對象,即系統內存分配。當一個對象的聲明周期要跟arena綁定,但它本身又不是在arena上分配時,這種情況下該方法很有用。
  • template<typename T> void OwnDestructor(T* object):將object的析構函數添加到arena的析構函數調用列表中。當arena銷毀時,它將遍歷整個列表並將調用每個析構函數。它不會試圖釋放對象的底層內存。當一個對象是內嵌在arena分配的存儲中但它的析構函數並不會被調用的情況下,該方法是有用的,例如,因為它的包含類是一個析構函數不會被調用的protobuf消息,或者是因為它是通過AllocateArray()手動在被分配的塊上構造的。

其它方法

  • uint64 SpaceUsed() const:返回arena的總大小,它是所有底層塊大小的總和。該方法時線程安全的;但是如果是多線程並發分配,該方法的返回值可能不包括那些新塊的大小。
  • uint64 Reset():銷毀arena存儲:首先調用所有注冊的析構函數且釋放所有注冊的堆對象,之后丟棄所有的arena塊。這個銷毀過程與arena的析構函數運行時發生的過程是等價的,只是arena在這個方法返回后會被重用。返回arena使用的總大小:此信息對於調優性能非常有用。
  • template<typename T> Arena* GetArena():返回arena的指針。雖然不是很有用,但它允許在需要GetArena()方法的模板實例化中使用Arena()

線程安全

google::protobuf::Arena的分配方法是線程安全的,且底層實現有一定的長度來確保多線程分配更快。Reset()方法不是線程安全的:執行arena重置的線程必須首先與所有執行分配或者使用arena中分配的對象的線程同步。

生成消息類

當你啟用arena分配時,下面的消息類成員會被改變或添加。

消息類方法

  • Message(Message&& other):如果源消息不在arena上,move構造高效地將一個消息的所有字段移動到另一個消息,而無需進行賦值或堆分配(該操作的時間復雜度為0(number-of-declared-fields))。但是如果源消息在arena上,它將執行底層數據的深拷貝操作。以上兩種情況中,源消息還是有效的但未指定的狀態。
  • Message& operator=(Message&& other):無論兩個消息都不在arena或是同一個arena,賦值操作高效地將一個消息的所有字段移動到另一個消息,而無需進行賦值或堆分配(該操作的時間復雜度為0(number-of-declared-fields))。如果只要一個消息在arena上或不在同一個arena上,它將執行底層數據的深拷貝操作。以上兩種情況中,源消息還是有效的但未指定的狀態。
  • void Swap(Message* other):如果要交換的兩個消息都不在arena或在同一個arena上,Swao()的行為與未啟用arena分配相同:它將高效地交換消息對象的內容,通常是通過廉價的指針交換以及盡可能地避免拷貝。如果只用一個消息在arena上或兩個消息不在同一個arena上,Swap()將執行底層數據的深拷貝操作。這一操作是很有必要的,因為交換之后的子對象可能有不同的生命周期,引起use-after-free錯誤。
  • Message* New(Arena* arena):對標准New()方法的替換重寫。它允許該類型在給定的arena上創建新的消息對象。他的語義與Arena::CreateMessage<T>(arena)相同,前提是它所調用的具體消息類型是在啟用Arena分配的情況下生成的。如果消息類型不是在啟用Arena分配的情況下生成的,當arena非空時,它等同於一個后跟arena->Own(message)的原始分配。
  • Arena* GetArena():返回分配此消息對象的arena(如果有的話)。
  • void UnsafeArenaSwap(Message* other):與Swap()相同,只是它假設兩個對象在同一個arena上(或兩個都不在arena上),並且總是使用這個操作的高效指針交換實現。使用該方法可以提升效率,因為,不像Swap(),在執行交換之前,它不需要檢查那個消息位於那個arena上。正如Unsafe前綴所說的,只有在確定消息不在不同的arena上時,你才能使用該方法;否則,該方法可能產生不可預測的結果。

內嵌消息字段

當你在arena上分配消息對象時,它的內嵌消息字段對象(子消息)自動為該arena所有。如何分配消息對象取決於它們在哪定義的:

  • 如果消息類型是在啟用Arena分配的.proto文件中定義的,則對象就直接在arena上分配。
  • 如果消息類型是在另一個沒有啟用Arena分配的.proto文件中定義的,該對象是在堆上分配的,但它的所有權歸父消息的arena所有。這意味着在arena銷毀時,屬於該arena的對象也會被釋放。

下列字段定義之一:

optional Bar foo = 1;
required Bar foo = 1;

在啟用arena分配情況下,下面的方法會被添加或有一些特殊的行為。否則,訪問器方法只是用默認行為

  • Bar* mutable_foo():返回子消息實例的可變指針。如果父對象在arena上,返回的對象也在arena上。
  • void set_allocated_foo(Bar* bar):接受一個新對象並將其作為字段的新值。Arena支持新增了額外的復制語義,來確保對象在跨越arena/arena或arena/heap邊界時保持適當的所有權:
    • 如果父對象在堆上且bar也在堆上,或父對象和子消息在同一arena上,該消息的行為不變。
    • 如果父對象在arena上且bar在堆上,父消息使用arena->Own()bar添加到它自己的arena所有去列表。
    • 如果父對象在arena上且bar在另一個arena上,該方法生成消息的副本將將其作為字段的新值對待。
  • Bar* release_foo():如果存在返回字段已存在的子消息實例,如果不存在則返回空指針;將該實例的所有權移交給調用者並清理父消息字段。Arena支持新增了額外的復制語義,以確保返回的對象總是遵守heap-allocated協議:
    • 如果父消息在arena上,該方法在堆上創建子消息的副本,清空該字段的值,並返回該副本。
    • 如果父消息在堆上,該消息行為不變。
  • void unsafe_arena_set_allocated_foo(Bar* bar):與set_allocated_foo相同,但假定父消息和子消息都在同一個arena。使用該方法可以提升性能,因為它不需要檢查消息是不是在特定的arena或堆上。只有在確定父消息在arena上且子消息也在同一arena上(或聲明周期與該arena相同)才能使用該方法。
  • Bar* unsafe_arena_release_foo():與release_foo()類似,但假定父消息在arena上,且返回一個不應該被直接刪除的arena-allocated對象。只有當父消息在arena上時才能使用該方法。

字符串字段

目前,即使父消息在arena上,字符串字段也將它們的數據存儲在堆上。因此,即使arena分配啟用,字符串訪問器方法使用默認行為

當arena啟用,字符串和字節字段生成unsafe_arena_release_field()unsafe_arena_set)allocated_field()方法。注意這些方法已被棄用,且之后會被刪除。這些方法是被錯誤地添加的,與它們的安全方法相比並沒有性能優勢。

重復字段

當包含的消息是在arena上分配的,重復字段也在arena上分配它們的內部數組存儲;當這些元素是由指針(消息或字符串)保留的獨立對象時,也在arena上分配它們的元素。在消息類級別,為重復字段生成的方法不變。在arena支持啟用時,由訪問器返回的RepeatedFieldRepeatedPtrField對象確實有新的方法和語義的改變。

重復數值字段

在arena支持啟用時,包含原始類型RepeatedField對象有下列新/變化的方法:

  • void UnsafeArenaSwap(RepeatedField* other):在無需驗證該重復字段和另一個是不是在同一個arena的情況下執行RepeatedField內容的交換。如果它們不在同一個arena上,那這兩個重復字段對象必須在生命周期相同的arena上。一個在arena上另一個在堆上的情況下,先檢查然后禁用。
  • void Swap(RepeatedField* other):檢查每個重復字段對象的arena,如果一個在arena另一個在堆上或兩個都在不同的arena上,在交換之前復制底層數組。這意味着交換結束后,每個重復字段都在它自己的arena或堆上保留一個合適的數組。
重復的內嵌消息字段

在arena支持啟用時,包含消息的RepeatedPtrField對象有下列新/變化的方法:

  • void UnsafeArenaSwap(RepeatedPtrField* other)::在無需驗證該重復字段和另一個是不是在同一個arena指針的情況下執行RepeatedField內容的交換。如果它們不在同一個arena指針上,那這兩個重復字段對象必須在生命周期相同的arena指針上。一個對象有一個非空的arena上指針而另一個有一個空的arena指針,這種情況下先檢查然后禁用。
  • void Swap(RepeatedPtrField* other):檢查每個重復字段對象的arena指針,如果一個非空(包含在arena上),另一個為空(包含在堆上),或兩個都為非空但值不同,在交換之前會復制底層數組和指向對象的指針。這意味着交換結束后,每個重復字段都在它自己的arena或堆上保留一個合適的數組。
  • void AddAllocated(SubMessageType* value):檢查給定的消息對象與重復字段的arena指針是不是一樣。如果實在同一個arena上,之后對象的指針就直接添加到底層數組。否則,會生成一個副本,如果對象實在堆上分配的原始的對象會被釋放,副本會被放入底層數組。這保證重復字段所指向的所有對象的指針與重復字段的arena指針指向的所有權域(堆或這指定的arena)相同。
  • SubMessageType* ReleaseLast():返回與重復字段最后一個消息相同的堆上分配的消息,並從重復字段中移除它。如果重復字段本身有一個空的arena指針(即它所有指向消息的指針都時堆分配的),之后該方法只是簡單的返回原始對象的指針。如果重復字段有一個非空的arena指針,該方法會在堆上分配一個副本然后返回該副本。上述兩種情況下,調用者會的堆上分配的對象的所有權,並負責刪除該對象。
  • void UnsafeArenaAddAllocated(SubMessageType* value):與AddAllocated()類似,但是不處理堆/arena檢查或任何消息的副本。它直接將提供的指針添加到該重復字段的內部數組指針中。如果重復字段有一個空的arena指針,調用者必須保證提供的對象是在堆上分配的,或者如果重復字段有一個非空的arena指針,則必須在arena分配(同一個arena或相同生命周期的arena)的。
  • SubMessageType* UnsafeArenaReleaseLast():與ReleaseLast()類似,但不處理任何副本,即使重復字段有一個非空的arena指針。相反,它直接返回該對象在重復字段中的指針。如果重復字段的arena指針為空,返回的對象是在堆上的,如果重復字段的arena指針非空,則是在arena上。如果對象是堆分配的,調用者獲得所有權;如果對象是arena分配的,調用者不能刪除返回的對象。
  • void ExtractSubrange(int start, int num, SubMessageType** elements):從索引start位置開始,從重復字段中提取num個元素,如果elements非空的話將移除的元素放入elememts中。如果重復字段在arena上,在元素返回之前先將這些元素復制到堆上。這兩中情況下(在或不在arena上),調用者擁有堆上的返回對象。
  • void UnsafeArenaExtractSubrange(int start, int num, SubMessageType** elements):從索引start位置開始,從重復字段中提取num個元素,如果elements非空的話將移除的元素放入elememts中。與ExtractSubrange()不同,該方法從不復制提取的元素。
重復的字符串字段

重復的字符串字段有與重復的消息字段一樣的新方法和修改的語義,因為它們都是通過指針引用來保存它們的底層數據(即字符串)。

使用模式和最佳實踐

在使用arena分配的消息時,有幾種使用模式可能導致意外的副本或其它負面性能的影響。你應該留意,下面的幾種常用的模式在適配arena的代碼時需要要有所改變。(注意,在API設計中我們已經注意到這一點,以確保依然正確的行為 --- 但是更高性能的解決方案仍可能需要一些返工。)

意外的副本

在啟用arena時,有些在未啟用arena時並不會創建對象副本的方法可能會創建副本。如果你確保分配的對象合適/或使用提供的arena特定版本的方法,就可以避免這些不需要的副本,下面將對此進行更詳細的描述。

設置分配/添加分配/釋放

默認情況下,release_field()set_allocate_field()方法(針對單個消息字段)以及ReleaseLast()AddAllocated()方法(針對重復消息字段)允許用戶代碼直接添加和分離子消息,通過指針的所有權而無需復制任何數據。

然爾,當父消息在arena上時,這些方法有時候需要復制傳入/返回的對象,以保持與現有的所有權兼容。更具體的,當父消息在arena上而新的子消息不在,獲取所有權的方法(set_allocated_field()AddAllocated())可能會復制數據;反之亦然;或它們不在同一arena上。如果父消息在arena上,釋放所有權的方法(release_field()ReleaseLast())可能會復制數據,因為按照約定返回的對象必須是在堆上。

為了避免這些復制,我們添加了這些方法的unsafe arena版本的協議,在這些版本中復制絕不會被執行:對單個和重復字段,分別是unsafe_arena_set_allocated_field()unsafe_arena_release_field()UnsafeArenaAddAllocated()UnsafeArenaRelease()。只有在你了解這么做是安全且父對象和子對象都能如期分配是,你才能使用這些方法。否則,比如,你可能獲得具有不同生命周期的父對象和子對象,這將導致use-after-free錯誤。

下面是你如何使用這些方法來避免不必要的復制的例子。接下來會在arena上創建下面的消息:

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

MyFeatureMessage* arena_message_2 =
  google::protobuf::Arena::CreateMessage<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->set_allocated_nested_message(
   arena_message_1->unsafe_arena_release_nested_message());

關於這些方法的更多細節,你可以在上面的內嵌消息字段章節了解到。

交換

如果兩個消息處在不同的arenas上,或一個在arena另一個在堆上,使用Swap()交換兩個消息的內容時,底層的子對象可能會被復制。如果你想避免這個復制,且知道這兩個消息在同一個arena上或在有相同生命周期的不同arena上,或知道這兩個消息都在堆上,你可以使用新的方法 --- UnsafeArenaSwap()。此方法既避免了執行arena檢查的開銷,又避免了在可能發生副本檢查的情況下進行副本檢查。

例如,下面的代碼在Swap()調用中會引起復制:

MyFeatureMessage* message_1 =
  google::protobuf::Arena::CreateMessage<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!

在上述代碼中要避免復制,你可以在相同的arena像message_1一樣分配message_2

MyFeatureMessage* message_2 =
   google::protobuf::Arena::CreateMessage<MyFeatureMessage>(arena);
內嵌消息字段和arena啟用選項

每個.proto都有它自己的arena支持的“功能開關”。如果給定的.proto文件中並沒有設置cc_enable_arenas,那么文件中的類型定義並不會存儲到arena上,即使其它類型中包含在了該文件中定義的子消息類型中。換言之,cc_enable_arenas是不可傳遞的。相反,具有arena資格但本身並不支持arena的消息的子消息將始終存儲在堆上,且會被添加到父消息的arena的Own()中以便將它們的生命周期與arena的綁定。

這樣約定的原因是,如果arena因為一些額外的代碼而未被使用,那么添加arena支持會增加一些開銷。所以我們選擇(目前)不啟用arena的全局支持。而且,由於類型和API兼容性的原因,每個proto消息類型只能有一個C++生成的類,因此我們不能同時生成帶arena支持和不帶arena支持的類版本。未來,功能優化之后,我們可能會取消這個限制,全局啟用arena支持。不過現在,應該為盡可能多的子消息啟用它以提高性能。

粒度

我們發現在大多數服務使用場景中,“arena-per-request”模式表現良好。你可能會嘗試進一步擴大arena使用,以減少堆開銷(通過更頻繁的銷毀較小的arena),或者減少感知到的線程競爭問題。然而就如我們上面所說的,使用更細粒度的arena可能會導致意外的消息復制。我們還為多線程用例優化了Arena實現,因此單個的arena應該適合在整個請求的生命周期中使用,即使是多線程處理該請求。

示例

下面是一個簡單的完整示例,演示了arena分配API的一些特性。

// my_feature.proto

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

package feature_package;

option cc_enable_arenas = true;

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

syntax = "proto2";

package feature_package;

// add cc_enable_arenas on each submessage for
// the best performance when using arenas.
option cc_enable_arenas = true;

// 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::CreateMessage<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);

更多信息,參見這里


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM