Wir verwenden protobuf-net für die Serialisierung und Deserialisierung von Nachrichten in einer Anwendung, deren öffentliches Protokoll auf Google Protocol Buffers basiert. Die Bibliothek ist exzellent und deckt alle unsere Anforderungen ab, außer für diese: Wir müssen die serialisierte Nachrichtenlänge in Bytes herausfinden, bevor die Nachricht tatsächlich serialisiert wird.
Die Frage wurde bereits vor anderthalb Jahren gestellt Laut Marc war der einzige Weg, dies zu tun, serialisieren zu einem MemoryStream und lesen Sie die Eigenschaft .Length
danach. Dies ist in unserem Fall nicht akzeptabel, da MemoryStream hinter den Kulissen einen Byte-Puffer reserviert und wir dies vermeiden müssen.
Diese Zeile aus derselben Antwort gibt uns Hoffnung, dass es doch möglich sein könnte:
Wenn Sie klarstellen, was der Anwendungsfall ist, können wir das sicher machen verfügbar (wenn es nicht bereits ist).
Hier ist unser Anwendungsfall. Wir haben Nachrichten, deren Größe zwischen einigen Bytes und zwei Megabyte variiert. Die Anwendung weist Byte-Puffer vor, die für Socket-Operationen und für die Serialisierung / Deserialisierung verwendet werden, und sobald die Aufwärmphase vorüber ist, können keine zusätzlichen Puffer erstellt werden (Hinweis: GC- und Heap-Fragmentierung überschreiben). Byte-Puffer werden im Wesentlichen gepoolt. Wir wollen auch vermeiden, Bytes zwischen Puffern / Streams so viel wie möglich zu kopieren.
Wir haben uns zwei mögliche Strategien ausgedacht, und beide erfordern vorab eine Nachrichtengröße:
Socket.Send
. Wir müssen wissen, wann die nächste Nachricht nicht in den Puffer passen und die Serialisierung stoppen kann. Ohne Nachrichtengröße ist der einzige Weg, dies zu erreichen, das Warten auf eine Ausnahme während Serialize
. Socket.Send
. Um den Byte-Puffer mit der passenden Größe aus dem Pool auszuchecken, müssen wir wissen, wie viele Bytes eine serialisierte Nachricht hat. Da das Protokoll bereits definiert ist (dies kann nicht geändert werden) und das Präfix für die Nachrichtenlänge Varint32 ist, können wir SerializeWithLengthPrefix
method nicht verwenden.
Ist es also möglich, eine Methode hinzuzufügen, die eine Nachrichtengröße ohne Serialisierung in einen Stream einschätzt? Wenn es etwas ist, das nicht in den aktuellen Funktionsumfang und die Roadmap der Bibliothek passt, aber machbar ist, sind wir daran interessiert, die Bibliothek selbst zu erweitern. Wir suchen auch nach alternativen Ansätzen, wenn es welche gibt.
Wie bereits erwähnt, ist dies nicht sofort verfügbar, da der Code absichtlich versucht, einen einzelnen Durchlauf über die Daten durchzuführen (insbesondere IEnumerable<T>
etc). Abhängig von Ihren Daten kann bereits eine moderate Menge an Kopien erstellen, um der Tatsache Rechnung zu tragen, dass Unter-Nachrichten auch Länge-Präfix haben, also möglicherweise benötigen Jonglieren. Dieses Jonglieren kann stark reduziert werden, indem das "gruppierte" Unterformat intern in der Nachricht verwendet wird, da Gruppen nur Vorwärtskonstruktionen ohne Trackbacks erlauben.
Ist es also möglich, eine Methode hinzuzufügen, die eine Nachrichtengröße ohne Serialisierung in einen Stream einschätzt?
Eine Schätzung ist neben nutzlos; Da es keinen Terminator gibt, muss er genau sein. Letztlich sind die Größen ein wenig schwer vorherzusagen, ohne es tatsächlich zu tun. Es gab Code in v1 für die Größenvorhersage, aber der Single-Pass-Code scheint derzeit vorzuziehen, und in den meisten Fällen ist der Puffer-Overhead nominal (es gibt Code, um die internen Puffer wiederzuverwenden, damit nicht alle ausgegeben werden) die Zeit, die Puffer für kleine Nachrichten reserviert).
Wenn Ihre Nachricht intern nur vorwärts (gruppiert) ist, könnte ein Cheat sein, zu einem gefälschten Stream zu serialisieren, der misst , aber alle Daten löscht; Sie würden jedoch zweimal hintereinander serialisieren.
Re:
und erfordert, dass das Nachrichtenlängen-Präfix Varint32 ist, wir können
nicht verwendenSerializeWithLengthPrefix
method
Ich bin mir nicht ganz sicher, ob ich die Beziehung dort sehe - es erlaubt eine Reihe von Formaten usw., die hier verwendet werden können; vielleicht, wenn Sie genauer sein können?
Um Daten zu kopieren - eine Idee, mit der ich hier gespielt habe, ist die Verwendung von subnormalen Formularen für das Längenpräfix. Zum Beispiel könnte es sein, dass in den meisten Fällen 5 Bytes viel sind, also statt jonglieren , es könnte 5 Bytes hinterlassen und dann ohne kondensieren (seit dem Oktett 10000000
bedeutet immer noch "Null und weiter", auch wenn es redundant ist. Dies müsste immer noch gepuffert werden (um eine Nachfüllung zu ermöglichen), würde aber keine Daten erfordern und verschieben.
Eine letzte einfache Idee wäre einfach: serialize to a FileStream
; Schreibe dann die Dateilänge und die Dateidaten. Es tauscht natürlich die Speichernutzung für IO aus.
Tags und Links c# protobuf-net