C++ Arena Allocation Guide
Diese Seite beschreibt genau, welchen C++-Code der Protocol-Buffer-Compiler zusätzlich zu dem im C++ Generated Code Guide beschriebenen Code generiert, wenn die Arena-Allokation aktiviert ist. Es wird davon ausgegangen, dass Sie mit den Inhalten des Sprachleitfadens und des C++ Generated Code Guide vertraut sind.
Warum Arena-Allokation verwenden?
Speicherallokation und -deallokation machen einen erheblichen Teil der CPU-Zeit aus, die im Protocol-Buffers-Code verbracht wird. Standardmäßig führt Protocol Buffers Heap-Allokationen für jedes Nachrichtenobjekt, jeden seiner Unterobjekte und mehrere Feldtypen wie Strings durch. Diese Allokationen erfolgen in großen Mengen beim Parsen einer Nachricht und beim Erstellen neuer Nachrichten im Speicher, und die zugehörigen Deallokationen erfolgen, wenn Nachrichten und ihre Unterobjektbäume freigegeben werden.
Die Arena-basierte Allokation wurde entwickelt, um diese Leistungskosten zu reduzieren. Mit der Arena-Allokation werden neue Objekte aus einem großen Stück vorallokierten Speichers namens Arena alloziert. Objekte können auf einmal freigegeben werden, indem die gesamte Arena verworfen wird, idealerweise ohne die Destruktoren der enthaltenen Objekte auszuführen (obwohl eine Arena bei Bedarf immer noch eine „Destruktorenliste“ pflegen kann). Dies beschleunigt die Objektallokation, indem sie auf eine einfache Zeigererhöhung reduziert wird, und macht die Deallokation fast kostenlos. Die Arena-Allokation bietet auch eine höhere Cache-Effizienz: Wenn Nachrichten geparst werden, werden sie mit höherer Wahrscheinlichkeit in kontinuierlichem Speicher alloziert, was dazu führt, dass das Durchlaufen von Nachrichten mit höherer Wahrscheinlichkeit auf heiße Cache-Zeilen trifft.
Um diese Vorteile zu nutzen, müssen Sie sich der Objektlebensdauern bewusst sein und eine geeignete Granularität für die Verwendung von Arenen finden (für Server ist dies oft pro Anfrage). Weitere Informationen darüber, wie Sie die Arena-Allokation optimal nutzen können, finden Sie unter Nutzungsmuster und Best Practices.
Diese Tabelle fasst die typischen Leistungs- und Nachteilvorteile der Verwendung von Arenen zusammen
| Operation | Heap-alloziierte Proto-Nachrichten | Arena-alloziierte Proto-Nachrichten |
|---|---|---|
| Nachrichtenallokation | langsamer im Durchschnitt | schneller im Durchschnitt |
| Nachrichtenzerstörung | langsamer im Durchschnitt | schneller im Durchschnitt |
| Nachrichtenverschiebungen | Immer eine Verschiebung (kostengleich einer flachen Kopie) | Manchmal eine tiefe Kopie |
Erste Schritte
Der Protocol-Buffer-Compiler generiert Code für die Arena-Allokation für die Nachrichten in Ihrer Datei, wie im folgenden Beispiel gezeigt.
#include <google/protobuf/arena.h>
{
google::protobuf::Arena arena;
MyMessage* message = google::protobuf::Arena::Create<MyMessage>(&arena);
// ...
}
Das von Create() erstellte Nachrichtenobjekt existiert so lange wie arena existiert, und Sie sollten den zurückgegebenen Nachrichtenzeiger nicht löschen. Der gesamte interne Speicher des Nachrichtenobjekts (mit wenigen Ausnahmen1) und Unternachrichten (z. B. Unternachrichten in einem wiederholten Feld innerhalb von MyMessage) werden ebenfalls auf der Arena alloziert.
Für den größten Teil wird der Rest Ihres Codes derselbe sein, als ob Sie keine Arena-Allokation verwenden würden.
Wir werden die Arena-API in den folgenden Abschnitten genauer betrachten und ein umfassenderes Beispiel am Ende des Dokuments sehen.
Arena-Klassen-API
Sie erstellen Nachrichtenobjekte auf der Arena mithilfe der Klasse google::protobuf::Arena. Diese Klasse implementiert die folgenden öffentlichen Methoden.
Konstruktoren
Arena(): Erstellt eine neue Arena mit Standardparametern, optimiert für durchschnittliche Anwendungsfälle.Arena(const ArenaOptions& options): Erstellt eine neue Arena, die die angegebenen Allokationsoptionen verwendet. Zu den inArenaOptionsverfügbaren Optionen gehören die Möglichkeit, einen anfänglichen Block von benutzergesteuertem Speicher für Allokationen zu verwenden, bevor auf den Systemallokator zurückgegriffen wird, die Steuerung der anfänglichen und maximalen Anforderungsgrößen für Speicherblöcke und die Möglichkeit, benutzerdefinierte Blockallokations- und Deallokationsfunktionszeiger zu übergeben, um Freilisten und andere Strukturen auf den Blöcken aufzubauen.
Allokationsmethoden
template<typename T> static T* Create(Arena* arena)odertemplate<typename T> static T* Create(Arena* arena, args...)Wenn
Tvollständig kompatibel2 ist, erstellt die Methode ein neues Protokollpufferobjekt vom TypTund seine Unterobjekte auf der Arena.Wenn
arenanicht NULL ist, wird das zurückgegebene Objekt auf der Arena alloziert, sein interner Speicher und seine Untertypen (falls vorhanden) werden auf derselben Arena alloziert, und seine Lebensdauer ist dieselbe wie die der Arena. Das Objekt darf nicht manuell gelöscht/freigegeben werden: Die Arena besitzt das Objekt für Lebensdauerzwecke.Wenn
arenaNULL ist, wird das zurückgegebene Objekt auf dem Heap alloziert, und der Aufrufer besitzt das Objekt nach der Rückgabe.Wenn
Tein Benutzertyp ist, ermöglicht die Methode die Erstellung eines Objekts, aber nicht der Unterobjekte auf der Arena. Angenommen, Sie haben diese C++-Klasseclass MyCustomClass { MyCustomClass(int arg1, int arg2); // ... };... Sie können eine Instanz davon auf der Arena wie folgt erstellen
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): Wennarenanicht NULL ist, alloziert diese Methode rohen Speicher fürnElemente des TypsTund gibt ihn zurück. Die Arena besitzt den zurückgegebenen Speicher und gibt ihn bei ihrer Zerstörung frei. WennarenaNULL ist, alloziert diese Methode Speicher auf dem Heap und der Aufrufer erhält das Eigentum.Tmuss einen trivialen Konstruktor haben: Konstruktoren werden nicht aufgerufen, wenn das Array auf der Arena erstellt wird.
„Owned list“-Methoden
Die folgenden Methoden ermöglichen es Ihnen anzugeben, dass bestimmte Objekte oder Destruktoren von der Arena „besessen“ werden, um sicherzustellen, dass sie beim Löschen der Arena selbst gelöscht oder aufgerufen werden
template<typename T> void Own(T* object): Fügtobjectder Liste der von der Arena besessenen Heap-Objekte hinzu. Wenn die Arena zerstört wird, durchläuft sie diese Liste und gibt jedes Objekt mithilfe von operator delete frei, d.h. den System-Speicherallokator. Diese Methode ist nützlich in Fällen, in denen die Lebensdauer eines Objekts mit der Arena verknüpft sein soll, aber das Objekt selbst aus irgendeinem Grund nicht auf der Arena alloziert werden kann oder wurde.template<typename T> void OwnDestructor(T* object): Fügt den Destruktor vonobjectder Liste der aufzurufenden Destruktoren der Arena hinzu. Wenn die Arena zerstört wird, durchläuft sie diese Liste und ruft jeden Destruktor der Reihe nach auf. Sie versucht nicht, den zugrunde liegenden Speicher des Objekts freizugeben. Diese Methode ist nützlich, wenn ein Objekt in einem Arena-allozierten Speicher eingebettet ist, aber sein Destruktor sonst nicht aufgerufen wird, z. B. weil seine enthaltende Klasse eine Protokollpuffernachricht ist, deren Destruktor nicht aufgerufen wird, oder weil es manuell in einem vonAllocateArray()allozierten Block konstruiert wurde.
Andere Methoden
uint64 SpaceUsed() const: Gibt die Gesamtgröße der Arena zurück, die die Summe der Größen der zugrunde liegenden Blöcke ist. Diese Methode ist thread-sicher; wenn jedoch gleichzeitige Allokationen von mehreren Threads erfolgen, enthält der Rückgabewert dieser Methode möglicherweise nicht die Größen dieser neuen Blöcke.uint64 Reset(): Zerstört den Speicher der Arena, ruft zuerst alle registrierten Destruktoren auf, gibt alle registrierten Heap-Objekte frei und verwirft dann alle Arena-Blöcke. Dieses Abbauverfahren entspricht dem, was beim Ausführen des Destruktors der Arena geschieht, außer dass die Arena nach Rückgabe dieser Methode für neue Allokationen wiederverwendbar ist. Gibt die von der Arena verwendete Gesamtgröße zurück: Diese Informationen sind nützlich für die Abstimmung der Leistung.template<typename T> Arena* GetArena(): Gibt einen Zeiger auf diese Arena zurück. Nicht direkt sehr nützlich, aber ermöglicht die Verwendung vonArenain Templatin-Instanziierungen, dieGetArena()-Methoden erwarten.
Threadsicherheit
Die Allokationsmethoden von google::protobuf::Arena sind thread-sicher, und die zugrunde liegende Implementierung unternimmt einiges, um die Multithreading-Allokation schnell zu gestalten. Die Methode Reset() ist **nicht** thread-sicher: Der Thread, der das Zurücksetzen der Arena durchführt, muss sich zuerst mit allen Threads synchronisieren, die Allokationen durchführen oder Objekte verwenden, die aus dieser Arena alloziert wurden.
Generierte Nachrichtenklasse
Die folgenden Member der Nachrichtenklasse werden geändert oder hinzugefügt, wenn Sie die Arena-Allokation aktivieren.
Nachrichtenklassen-Methoden
Message(Message&& other): Wenn die Quellnachricht nicht auf einer Arena liegt, verschiebt der Move-Konstruktor effizient alle Felder von einer Nachricht in eine andere, ohne Kopien oder Heap-Allokationen vorzunehmen (die Zeitkomplexität dieser Operation istO(Anzahl der deklarierten Felder)). Wenn die Quellnachricht jedoch auf einer Arena liegt, führt sie eine tiefe Kopie der zugrunde liegenden Daten durch. In beiden Fällen bleibt die Quellnachricht in einem gültigen, aber nicht spezifizierten Zustand.Message& operator=(Message&& other): Wenn beide Nachrichten nicht auf einer Arena liegen oder auf *derselben* Arena liegen, verschiebt der Move-Zuweisungsoperator effizient alle Felder von einer Nachricht in eine andere, ohne Kopien oder Heap-Allokationen vorzunehmen (die Zeitkomplexität dieser Operation istO(Anzahl der deklarierten Felder)). Wenn jedoch nur eine Nachricht auf einer Arena liegt oder die Nachrichten auf verschiedenen Arenen liegen, führt sie eine tiefe Kopie der zugrunde liegenden Daten durch. In beiden Fällen bleibt die Quellnachricht in einem gültigen, aber nicht spezifizierten Zustand.void Swap(Message* other): Wenn die Inhalte beider zu tauschender Nachrichten nicht auf Arenen liegen oder auf *derselben* Arena liegen, verhält sichSwap()wie ohne aktivierte Arena-Allokation: Es tauscht effizient die Inhalte der Nachrichtenobjekte aus, fast ausschließlich durch kostengünstige Zeigertausche, wodurch Kopien vermieden werden. Wenn jedoch nur eine Nachricht auf einer Arena liegt oder die Nachrichten auf verschiedenen Arenen liegen, führtSwap()tiefe Kopien der zugrunde liegenden Daten durch. Dieses neue Verhalten ist notwendig, da sonst die getauschten Unterobjekte unterschiedliche Lebensdauern haben könnten, was potenziell zu Use-after-free-Fehlern führen kann.Message* New(Arena* arena): Eine alternative Überschreibung der StandardmethodeNew(). Sie ermöglicht die Erstellung eines neuen Nachrichtenobjekts dieses Typs auf der angegebenen Arena. Ihre Semantik ist identisch mitArena::Create<T>(arena), wenn der konkrete Nachrichtentyp, auf dem sie aufgerufen wird, mit aktivierter Arena-Allokation generiert wurde. Wenn der Nachrichtentyp nicht mit aktivierter Arena-Allokation generiert wurde, ist er äquivalent zu einer normalen Allokation gefolgt vonarena->Own(message), wennarenanicht NULL ist.Arena* GetArena(): Gibt die Arena zurück, auf der dieses Nachrichtenobjekt alloziert wurde, falls vorhanden.void UnsafeArenaSwap(Message* other): Identisch mitSwap(), mit der Ausnahme, dass angenommen wird, dass beide Objekte auf derselben Arena liegen (oder gar nicht auf Arenen liegen) und immer die effiziente Zeiger-Austauschimplementierung dieser Operation verwendet. Die Verwendung dieser Methode kann die Leistung verbessern, da sie im Gegensatz zuSwap()nicht überprüfen muss, welche Nachrichten auf welcher Arena liegen, bevor der Tausch durchgeführt wird. Wie das PräfixUnsafeandeutet, sollten Sie diese Methode nur verwenden, wenn Sie sicher sind, dass die zu tauschenden Nachrichten nicht auf verschiedenen Arenen liegen; andernfalls kann diese Methode zu unvorhersehbaren Ergebnissen führen.
Eingebettete Nachrichtenfelder
Wenn Sie ein Nachrichtenobjekt auf einer Arena alloziieren, werden seine eingebetteten Nachrichtenobjekte (Unternachrichten) ebenfalls automatisch von der Arena besessen. Wie diese Nachrichtenobjekte alloziert werden, hängt davon ab, wo sie definiert sind
- Wenn der Nachrichtentyp ebenfalls in einer
.proto-Datei mit aktivierter Arena-Allokation definiert ist, wird das Objekt direkt auf der Arena alloziert. - Wenn der Nachrichtentyp aus einer anderen
.proto-Datei ohne aktivierte Arena-Allokation stammt, wird das Objekt auf dem Heap alloziert, aber von der Arena der Elternnachricht „besessen“. Dies bedeutet, dass beim Zerstören der Arena das Objekt zusammen mit den Objekten auf der Arena selbst freigegeben wird.
Für die Felddefinition
Bar foo = 1;
Die folgenden Methoden werden hinzugefügt oder haben ein spezielles Verhalten, wenn die Arena-Allokation aktiviert ist. Andernfalls verwenden die Zugriffsmethoden einfach das Standardverhalten.
Bar* mutable_foo(): Gibt einen veränderbaren Zeiger auf die Unterinstanz der Nachricht zurück. Wenn das Elternobjekt auf einer Arena liegt, wird das zurückgegebene Objekt ebenfalls darauf liegen.void set_allocated_foo(Bar* bar): Nimmt ein neues Objekt und übernimmt es als neuen Wert für das Feld. Die Arena-Unterstützung fügt zusätzliche Kopiersemantik hinzu, um die richtige Eigentümerschaft zu wahren, wenn Objekte Arena/Arena- oder Arena/Heap-Grenzen überschreiten- Wenn das Elternobjekt auf dem Heap liegt und
barauf dem Heap liegt, oder wenn das Elternobjekt und die Nachricht auf derselben Arena liegen, bleibt das Verhalten dieser Methode unverändert. - Wenn das Elternobjekt auf einer Arena liegt und
barauf dem Heap liegt, fügt die Elternnachrichtbarmitarena->Own()zur Eigentümerliste ihrer Arena hinzu. - Wenn das Elternobjekt auf einer Arena liegt und
barauf einer anderen Arena liegt, erstellt diese Methode eine Kopie der Nachricht und übernimmt die Kopie als neuen Feldwert.
- Wenn das Elternobjekt auf dem Heap liegt und
Bar* release_foo(): Gibt die vorhandene Unterinstanz der Nachricht des Feldes zurück, falls gesetzt, oder einen NULL-Zeiger, falls nicht gesetzt, gibt die Eigentümerschaft dieser Instanz an den Aufrufer ab und leert das Feld der Elternnachricht. Die Arena-Unterstützung fügt zusätzliche Kopiersemantik hinzu, um den Vertrag einzuhalten, dass das zurückgegebene Objekt immer heap-alloziert ist- Wenn die Elternnachricht auf einer Arena liegt, erstellt diese Methode eine Kopie der Unterachricht auf dem Heap, leert den Feldwert und gibt die Kopie zurück.
- Wenn die Elternnachricht auf dem Heap liegt, bleibt das Verhalten der Methode unverändert.
void unsafe_arena_set_allocated_foo(Bar* bar): Identisch mitset_allocated_foo, aber nimmt an, dass sowohl Eltern- als auch Unterobjekt auf derselben Arena liegen. Die Verwendung dieser Version der Methode kann die Leistung verbessern, da sie nicht prüfen muss, ob die Nachrichten auf einer bestimmten Arena oder dem Heap liegen. Siehe allocated/release-Muster für Details zu sicheren Verwendungsmöglichkeiten.Bar* unsafe_arena_release_foo(): Ähnlich wierelease_foo(), aber überspringt alle Eigentümerschaftsprüfungen. Siehe allocated/release-Muster für Details zu sicheren Verwendungsmöglichkeiten.
String-Felder
String-Felder speichern ihre Daten auf dem Heap, auch wenn ihre Elternnachricht auf der Arena liegt. Aus diesem Grund verwenden String-Zugriffsmethoden das Standardverhalten, auch wenn die Arena-Allokation aktiviert ist.
Wiederholte Felder
Wiederholte Felder alloziieren ihren internen Array-Speicher auf der Arena, wenn die enthaltende Nachricht Arena-alloziert ist, und alloziieren auch ihre Elemente auf der Arena, wenn diese Elemente separate Objekte sind, die per Zeiger gehalten werden (Nachrichten oder Strings). Auf der Ebene der Nachrichtenklasse ändern sich die generierten Methoden für wiederholte Felder nicht. Die Objekte RepeatedField und RepeatedPtrField, die von Accessoren zurückgegeben werden, haben jedoch neue Methoden und geänderte Semantiken, wenn die Arena-Unterstützung aktiviert ist.
Wiederholte numerische Felder
RepeatedField-Objekte, die primitive Typen enthalten, haben die folgenden neuen/geänderten Methoden, wenn die Arena-Allokation aktiviert ist
void UnsafeArenaSwap(RepeatedField* other): Führt einen Tausch derRepeatedField-Inhalte durch, ohne zu überprüfen, ob dieses wiederholte Feld und das andere auf derselben Arena liegen. Wenn nicht, müssen die beiden wiederholten Feldobjekte Arenen mit äquivalenten Lebensdauern haben. Der Fall, dass eines auf einer Arena und eines auf dem Heap liegt, wird überprüft und nicht zugelassen.void Swap(RepeatedField* other): Prüft die Arena jedes wiederholten Feldobjekts, und wenn eines auf einer Arena liegt, während eines auf dem Heap liegt, oder wenn beide auf Arenen liegen, aber auf unterschiedlichen, werden die zugrunde liegenden Arrays vor dem Tausch kopiert. Das bedeutet, dass nach dem Tausch jedes wiederholte Feldobjekt ein Array auf seiner eigenen Arena oder seinem eigenen Heap enthält, je nachdem.
Wiederholte eingebettete Nachrichtenfelder
RepeatedPtrField-Objekte, die Nachrichten enthalten, haben die folgenden neuen/geänderten Methoden, wenn die Arena-Allokation aktiviert ist.
void UnsafeArenaSwap(RepeatedPtrField* other): Führt einen Tausch derRepeatedPtrField-Inhalte durch, ohne zu überprüfen, ob dieses wiederholte Feld und das andere denselben Arena-Zeiger haben. Wenn nicht, müssen die beiden wiederholten Feldobjekte Arena-Zeiger mit äquivalenten Lebensdauern haben. Der Fall, dass eines einen nicht-NULL Arena-Zeiger hat und eines einen NULL Arena-Zeiger hat, wird überprüft und nicht zugelassen.void Swap(RepeatedPtrField* other): Prüft den Arena-Zeiger jedes wiederholten Feldobjekts, und wenn einer nicht NULL ist (Inhalte auf Arena) und einer NULL ist (Inhalte auf Heap) oder wenn beide nicht NULL sind, aber unterschiedliche Werte haben, werden die zugrunde liegenden Arrays und die von ihnen referenzierten Objekte vor dem Tausch kopiert. Dies bedeutet, dass nach dem Tausch jedes wiederholte Feldobjekt ein Array auf seiner eigenen Arena oder auf dem Heap enthält, je nachdem.void AddAllocated(SubMessageType* value): Prüft, ob das übergebene Nachrichtenobjekt auf derselben Arena wie der Arena-Zeiger des wiederholten Feldes liegt.- Quelle und Ziel sind beide Arena-alloziert und auf derselben Arena: Der Objektzeiger wird direkt zum zugrunde liegenden Array hinzugefügt.
- Quelle und Ziel sind beide Arena-alloziert und auf verschiedenen Arenen: Es wird eine Kopie erstellt, das Original wird freigegeben, wenn es heap-alloziert war, und die Kopie wird in das Array gelegt.
- Die Quelle ist heap-alloziert und das Ziel ist Arena-alloziert: Es wird keine Kopie erstellt.
- Die Quelle ist Arena-alloziert und das Ziel ist heap-alloziert: Es wird eine Kopie erstellt und in das Array gelegt.
- Quelle und Ziel sind beide heap-alloziert: Der Objektzeiger wird direkt zum zugrunde liegenden Array hinzugefügt.
Dies erhält die Invariante, dass alle von einem wiederholten Feld referenzierten Objekte sich in derselben Eigentümerdomäne (Heap oder spezifische Arena) befinden, wie durch den Arena-Zeiger des wiederholten Feldes angezeigt.
SubMessageType* ReleaseLast(): Gibt eine heap-alloziierte Nachricht zurück, die der letzten Nachricht im wiederholten Feld entspricht, und entfernt sie aus dem wiederholten Feld. Wenn das wiederholte Feld selbst einen NULL-Arena-Zeiger hat (und somit alle seine referenzierten Nachrichten heap-alloziert sind), gibt diese Methode einfach einen Zeiger auf das ursprüngliche Objekt zurück. Andernfalls, wenn das wiederholte Feld einen nicht-NULL Arena-Zeiger hat, erstellt diese Methode eine Kopie, die heap-alloziert ist, und gibt diese Kopie zurück. In beiden Fällen erhält der Aufrufer das Eigentum an einem heap-allozierten Objekt und ist für die Löschung des Objekts verantwortlich.void UnsafeArenaAddAllocated(SubMessageType* value): WieAddAllocated(), aber führt keine Heap/Arena-Prüfungen oder Nachrichtenkopien durch. Es fügt den bereitgestellten Zeiger direkt zum internen Zeigerarray für dieses wiederholte Feld hinzu. Siehe allocated/release-Muster für Details zu sicheren Verwendungsmöglichkeiten.SubMessageType* UnsafeArenaReleaseLast(): WieReleaseLast(), aber führt keine Kopien durch, auch wenn das wiederholte Feld einen nicht-NULL Arena-Zeiger hat. Stattdessen gibt es direkt den Zeiger auf das Objekt zurück, wie es sich im wiederholten Feld befand. Siehe allocated/release-Muster für Details zu sicheren Verwendungsmöglichkeiten.void ExtractSubrange(int start, int num, SubMessageType** elements): EntferntnumElemente aus dem wiederholten Feld, beginnend bei Indexstart, und gibt sie inelementszurück, falls dieser nicht NULL ist. Wenn das wiederholte Feld auf einer Arena liegt und Elemente zurückgegeben werden, werden die Elemente zuerst auf den Heap kopiert. In beiden Fällen (Arena oder keine Arena) besitzt der Aufrufer die zurückgegebenen Objekte auf dem Heap.void UnsafeArenaExtractSubrange(int start, int num, SubMessageType** elements): EntferntnumElemente aus dem wiederholten Feld, beginnend bei Indexstart, und gibt sie inelementszurück, falls dieser nicht NULL ist. Im Gegensatz zuExtractSubrange()kopiert diese Methode niemals die extrahierten Elemente. Siehe allocated/release-Muster für Details zu sicheren Verwendungsmöglichkeiten.
Wiederholte String-Felder
Wiederholte Felder von Strings haben dieselben neuen Methoden und modifizierten Semantiken wie wiederholte Felder von Nachrichten, da sie auch ihre zugrunde liegenden Objekte (nämlich Strings) per Zeigerreferenz verwalten.
Nutzungsmuster und Best Practices
Bei der Verwendung von Arena-allozierten Nachrichten können mehrere Nutzungsmuster zu unbeabsichtigten Kopien oder anderen negativen Leistungseffekten führen. Sie sollten sich der folgenden gängigen Muster bewusst sein, die möglicherweise geändert werden müssen, wenn Sie Code für Arenen anpassen. (Beachten Sie, dass wir bei der Gestaltung der API darauf geachtet haben, dass das korrekte Verhalten weiterhin auftritt – aber leistungsfähigere Lösungen erfordern möglicherweise einige Überarbeitungen.)
Unbeabsichtigte Kopien
Mehrere Methoden, die bei Nichtverwendung der Arena-Allokation niemals Objektkopien erstellen, tun dies möglicherweise, wenn die Arena-Unterstützung aktiviert ist. Diese unerwünschten Kopien können vermieden werden, wenn Sie sicherstellen, dass Ihre Objekte ordnungsgemäß alloziert werden und/oder die bereitgestellten Arena-spezifischen Methodenversionen verwenden, wie unten näher erläutert.
Set Allocated/Add Allocated/Release
Standardmäßig erlauben die Methoden release_field() und set_allocated_field() (für singuläre Nachrichtenfelder) sowie die Methoden ReleaseLast() und AddAllocated() (für wiederholte Nachrichtenfelder) dem Benutzercode, Unternachrichten direkt anzuhängen und zu lösen, wobei die Eigentümerschaft von Zeigern ohne Datenkopie übergeben wird.
Wenn die Elternnachricht jedoch auf einer Arena liegt, müssen diese Methoden manchmal das übergebene oder zurückgegebene Objekt kopieren, um die Kompatibilität mit bestehenden Eigentümerschaftsverträgen zu gewährleisten. Insbesondere Methoden, die die Eigentümerschaft übernehmen (set_allocated_field() und AddAllocated()), können Daten kopieren, wenn das Elternobjekt auf einer Arena liegt und das neue Unterobjekt nicht, oder umgekehrt, oder sie liegen auf unterschiedlichen Arenen. Methoden, die die Eigentümerschaft lösen (release_field() und ReleaseLast()), können Daten kopieren, wenn die Elternnachricht auf der Arena liegt, da das zurückgegebene Objekt per Vertrag auf dem Heap liegen muss.
Um solche Kopien zu vermeiden, wurden entsprechende „unsichere Arena“-Versionen dieser Methoden hinzugefügt, bei denen Kopien **niemals durchgeführt** werden: unsafe_arena_set_allocated_field(), unsafe_arena_release_field(), UnsafeArenaAddAllocated() und UnsafeArenaRelease() für singuläre bzw. wiederholte Felder. Diese Methoden sollten nur verwendet werden, wenn Sie wissen, dass sie sicher sind. Es gibt zwei gängige Muster für diese Methoden
- Verschieben von Nachrichtenbäumen zwischen Teilen derselben Arena. Beachten Sie, dass die Nachrichten auf derselben Arena liegen müssen, damit dieser Fall sicher ist.
- Vorübergehendes Ausleihen einer besessenen Nachricht an einen Baum, um Kopien zu vermeiden. Das Paaren einer unsicheren add/set-Methode mit einer unsicheren release-Methode führt die Ausleihe auf die kostengünstigste Weise durch, unabhängig davon, wie die Nachricht besessen wird (dieses Muster funktioniert, wenn sie auf derselben Arena, auf einer anderen Arena oder gar keiner Arena liegen). Beachten Sie, dass zwischen dem unsicheren add/set und seinem entsprechenden release der Borrwer nicht getauscht, verschoben, geleert oder zerstört werden darf; die geliehene Nachricht darf nicht getauscht oder verschoben werden; die geliehene Nachricht darf nicht vom Borrwer geleert oder freigegeben werden; und die geliehene Nachricht darf nicht zerstört werden.
Hier ist ein Beispiel, wie Sie unnötige Kopien mit diesen Methoden vermeiden können. Angenommen, Sie haben die folgenden Nachrichten auf einer Arena erstellt.
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);
Der folgende Code verwendet die release_...()-API ineffizient
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
Die Verwendung der „unsicheren Arena“-Version vermeidet stattdessen die Kopie
arena_message_2->unsafe_arena_set_allocated_nested_message(
arena_message_1->unsafe_arena_release_nested_message());
Weitere Informationen zu diesen Methoden finden Sie im Abschnitt Eingebettete Nachrichtenfelder oben.
Swap
Wenn die Inhalte zweier Nachrichten mit Swap() getauscht werden, können die zugrunde liegenden Unterobjekte kopiert werden, wenn die beiden Nachrichten auf verschiedenen Arenen liegen oder wenn eine auf der Arena und die andere auf dem Heap liegt. Wenn Sie diese Kopie vermeiden möchten und entweder (i) wissen, dass die beiden Nachrichten auf derselben Arena oder unterschiedlichen Arenen liegen, aber die Arenen äquivalente Lebensdauern haben, oder (ii) wissen, dass die beiden Nachrichten auf dem Heap liegen, können Sie eine neue Methode verwenden: UnsafeArenaSwap(). Diese Methode vermeidet sowohl den Overhead der Arenaprüfung als auch die Kopie, wenn eine aufgetreten wäre.
Zum Beispiel verursacht der folgende Code eine Kopie im Swap()-Aufruf
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!
Um die Kopie in diesem Code zu vermeiden, alloziieren Sie message_2 auf derselben Arena wie message_1
MyFeatureMessage* message_2 =
google::protobuf::Arena::Create<MyFeatureMessage>(arena);
Granularität
Wir haben in den meisten Anwendungsfällen von Anwendungsservern festgestellt, dass ein „Arena pro Anfrage“-Modell gut funktioniert. Möglicherweise sind Sie versucht, die Arenanutzung weiter zu unterteilen, entweder um den Heap-Overhead zu reduzieren (durch häufigeres Zerstören kleinerer Arenen) oder um wahrgenommene Thread-Konfliktprobleme zu reduzieren. Die Verwendung feingranularerer Arenen kann jedoch zu unbeabsichtigten Nachrichtenkopien führen, wie wir oben beschrieben haben. Wir haben auch Anstrengungen unternommen, die Arena-Implementierung für den Multithreading-Anwendungsfall zu optimieren, sodass eine einzelne Arena für die Verwendung während der gesamten Lebensdauer einer Anfrage angemessen sein sollte, auch wenn mehrere Threads diese Anfrage verarbeiten.
Beispiel
Hier ist ein einfaches vollständiges Beispiel, das einige der Funktionen der Arena-Allokations-API demonstriert.
// 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;
};
Nachrichtenkonstruktion und -deallokation
#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);
Derzeit speichern String-Felder ihre Daten auf dem Heap, auch wenn die enthaltende Nachricht auf der Arena liegt. Unbekannte Felder sind ebenfalls heap-alloziert. ↩︎
Was es bedeutet, ein „vollständig kompatibler“ Typ zu sein, ist intern für die Protocol Buffers-Bibliothek, und sollte nicht als zuverlässig angenommen werden. ↩︎