Speicherverwendungs-Serialisierung von Chunked-Byte-Arrays mit Protobuf-net

8

In unserer Anwendung haben wir einige Datenstrukturen, die unter anderem eine Chunked-Liste von Bytes enthalten (aktuell als List<byte[]> verfügbar). Wir teilen Bytes auf, denn wenn wir zulassen, dass die Byte-Arrays auf den großen Objekt-Heap gesetzt werden, leiden wir mit der Zeit unter Speicherfragmentierung.

Wir haben auch begonnen, diese Strukturen mithilfe von Protobuf-net zu serialisieren, indem wir unsere eigene generierte Serialisierungs-DLL verwenden.

Wir haben jedoch festgestellt, dass Protobuf-net beim Serialisieren sehr große In-Memory-Puffer erstellt. Beim Durchschauen des Quellcodes scheint es, dass er seinen internen Puffer nicht löschen kann, bis die gesamte List<byte[]> -Struktur geschrieben wurde, weil er danach die Gesamtlänge am Anfang des Puffers schreiben muss.

Das macht unsere Arbeit leider zunichte, indem wir die Bytes zuerst chunken und schließlich OutOfMemoryExceptions aufgrund von Speicherfragmentierung erhalten (die Ausnahme tritt zu dem Zeitpunkt auf, an dem Protobuf-net versucht, den Puffer auf über 84k zu erweitern) es auf der LOH, und unsere gesamte Prozessspeicherauslastung ist ziemlich niedrig).

Wenn meine Analyse der Funktionsweise von Protobuf-net richtig ist, gibt es einen Weg um dieses Problem?

Aktualisieren

Basierend auf Marc's Antwort, hier ist was ich versucht habe:

%Vor%

Dann, um es zu serialisieren:

%Vor%

Wenn ich jedoch einen Haltepunkt in ProtoWriter.WriteBytes() anlege, wo er DemandSpace() zum Ende der Methode aufruft und in DemandSpace() eintrete, kann ich sehen, dass der Puffer nicht geleert wird, weil writer.flushLock gleich co_de ist %.

Wenn ich eine andere Basisklasse für ABase wie folgt erstelle:

%Vor%

Dann ist 1 gleich writer.flushLock in 2 .

Ich vermute, dass es einen offensichtlichen Schritt gibt, den ich hier mit abgeleiteten Typen verpasst habe?

    
James Thurley 03.07.2012, 18:33
quelle

2 Antworten

5

Ich werde hier zwischen einigen Zeilen lesen ... weil List<T> (im protobuf-Sprachgebrauch als repeated zugeordnet) kein Gesamtlängenpräfix hat und byte[] (zugeordnet als bytes ) ) hat ein triviales Längenpräfix, das keine zusätzliche Pufferung verursachen sollte. Also ich vermute, was Sie eigentlich haben, ist eher wie:

%Vor%

Hier ist die Notwendigkeit für ein Längenpräfix zu puffern tatsächlich beim Schreiben von A.Foo , im Grunde deklarieren "die folgenden komplexen Daten sind der Wert für A.Foo "). Zum Glück gibt es eine einfache Lösung:

%Vor%

Dies ändert sich zwischen zwei Packtechniken in protobuf:

  • Der Standardwert (die angegebene Präferenz von google) hat eine Länge vor dem Präfix. Das bedeutet, dass Sie eine Markierung erhalten, die die Länge der zu folgenden Nachricht angibt, und dann die Nutzdaten der Unternachricht
  • Es gibt aber auch eine Option, einen Start-Marker, die Sub-Message-Payload und einen End-Marker
  • zu verwenden

Bei Verwendung der zweiten Technik muss kein gepuffert werden, also: tut es nicht. Dies bedeutet, dass es leicht unterschiedliche Bytes für die gleichen Daten schreiben wird, aber protobuf-net ist sehr fehlerverzeihend und wird die Daten aus dem entweder Format gerne deserialisieren. Bedeutung: Wenn Sie diese Änderung vornehmen, können Sie Ihre vorhandenen Daten weiterhin lesen, aber neue Daten verwenden die Start / Ende-Marker-Technik.

Dies stellt die Frage: Warum bevorzugt Google den Ansatz mit Längenvorwahl? Wahrscheinlich liegt das daran, dass beim Lesen effizienter ist, um Felder zu überspringen (entweder über eine Raw-Reader-API oder als unerwünschte / unerwartete Daten) , wie Sie nur das Längenpräfix lesen können, und dann nur die Stream [n] Bytes fortschreiten; Im Gegensatz dazu müssen Sie, um Daten mit einem Start- / End-Marker zu überspringen, immer noch durch die Payload crawlen und dabei die Unterfelder einzeln überspringen. Dieser theoretische Unterschied in der Leseperformance trifft natürlich nicht zu, wenn Sie diese Daten erwarten und in Ihr Objekt einlesen möchten, was Sie mit ziemlicher Sicherheit tun. Auch in der Google Protobuf-Implementierung, weil es nicht mit einem normalen POCO-Modell arbeitet, ist die Größe der Nutzdaten bereits bekannt, so dass sie beim Schreiben nicht das gleiche Problem sehen.

    
Marc Gravell 04.07.2012, 06:31
quelle
2

Zusätzlich zu Ihrer Bearbeitung; Das [ProtoInclude(..., DataFormat=...)] sieht so aus, als ob es einfach nicht verarbeitet wurde. Ich habe einen Test dafür in meinem aktuellen lokalen Build hinzugefügt, und es besteht nun:

%Vor%

Dieses Commit ist in einige andere, nicht verwandte Refactorings eingebunden (einige Nacharbeiten für WinRT / IKVM), sollten aber so schnell wie möglich übergeben werden.

    
Marc Gravell 08.07.2012 22:30
quelle