2016-01-16 8 views
5

Serialisierung Betrachten wir ein Cap'n'Proto Schema wie folgt aus:Strom, während sie mit Cap'n'Proto

struct Document { 
    header @0 : Header; 
    records @1 :List(Record); // usually large number of records. 
    footer @2 :Footer; 
} 
struct Header { numberOfRecords : UInt32; /* some fields */ }; 
struct Footer { /* some fields */ }; 
struct Record { 
    type : UInt32; 
    desc : Text; 
    /* some more fields, relatively large in total */ 
} 

Jetzt will ich serialisiert werden (das heißt build) eine Dokumentinstanz und streamen es zu einem entfernten Ziel.

Da das Dokument normalerweise sehr groß ist, möchte ich es nicht vollständig im Speicher erstellen, bevor ich es sende. Stattdessen suche ich nach einem Builder, der Struktur für Struktur direkt über die Leitung sendet. So dass der zusätzlich benötigte Speicherpuffer konstant ist (zB O (max (sizeof (Header), sizeof (Record), sizeof (Footer)).

Mit Blick auf das Tutorial-Material finde ich keinen solchen Builder. die MallocMessageBuilder scheint zunächst alles in Erinnerung zu erstellen (dann rufen Sie writeMessageToFd drauf).

ist die Cap'n'Proto API-Unterstützung ein solcher anwendungs~~POS=TRUNC?

Oder ist Cap'n'Proto mehr gemeint für Nachrichten, die in den Speicher passen, bevor sie gesendet werden?

In diesem Beispiel könnte die Dokumentstruktur weggelassen werden und dann weiter e könnte einfach eine Sequenz von einer Header-Nachricht, n Record-Nachrichten und einer Fußzeile senden. Da eine Cap'n'Proto-Nachricht sich selbst abgrenzt, sollte dies funktionieren. Aber du verlierst deine Dokumentenwurzel - vielleicht ist das manchmal nicht wirklich eine Option.

Antwort

7

Die von Ihnen beschriebene Lösung - das Senden der Teile des Dokuments als separate Nachrichten - ist wahrscheinlich am besten für Ihren Anwendungsfall geeignet. Grundsätzlich ist Cap'n Proto nicht dafür gedacht, Chunks einer einzelnen Nachricht zu streamen, da dies nicht zu den Random-Access-Eigenschaften passt (zB was passiert, wenn Sie versuchen, einem Zeiger zu folgen, der auf einen Chunk verweist, den Sie nicht erhalten haben) noch?). Wenn Sie jedoch Streaming wünschen, sollten Sie eine große Nachricht in eine Reihe kleinerer Nachrichten aufteilen.

Im Gegensatz zu anderen ähnlichen Systemen (z. B. Protobuf), erfordert Cap'n Proto nicht unbedingt, dass Nachrichten in den Speicher passen. Insbesondere können Sie einige Tricks mit mmap(2) tun. Wenn Ihre Dokumentendaten aus einer Datei auf der Festplatte stammen, können Sie die Datei in den Speicher kopieren und dann in Ihre Nachricht integrieren. Mit mmap() liest das Betriebssystem die Daten von der Festplatte nicht wirklich, bis Sie versuchen, auf den Speicher zuzugreifen, und das Betriebssystem kann die Seiten auch nach dem Zugriff aus dem Speicher löschen, da es weiß, dass es noch eine Kopie auf der Festplatte hat. Dadurch können Sie viel einfacher Code schreiben, da Sie nicht mehr über die Speicherverwaltung nachdenken müssen.

Um einen mmap() Ed-Chunk in eine Cap'n Proto-Nachricht zu integrieren, sollten Sie capnp::Orphanage::referenceExternalData() verwenden. Zum Beispiel gegeben:

struct MyDocument { 
    body @0 :Data; 
    # (other fields) 
} 

Sie schreiben könnten:

// Map file into memory. 
void* ptr = (kj::byte*)mmap(
    nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0); 
if (ptr == MAP_FAILED) { 
    KJ_FAIL_SYSCALL("mmap", errno); 
} 
auto data = capnp::Data::Reader((kj::byte*)ptr, size); 

// Incorporate it into a message. 
capnp::MallocMessageBuilder message; 
auto root = message.getRoot<MyDocument>(); 
root.adoptDocumentBody(
    message.getOrphanage().referenceExternalData(data)); 

Da Käpt'n Proto Null-Kopie, wird es die mmap() ed Speicher direkt in den Sockel am Ende zu schreiben, ohne jemals den Zugriff auf es. Es ist dann Sache des Betriebssystems, den Inhalt von der Festplatte und aus dem Socket zu lesen.

Natürlich haben Sie immer noch ein Problem auf der Empfängerseite. Sie werden es viel schwieriger finden, das Empfangsende zum Lesen in mmap() ED-Speicher zu entwerfen. Eine Strategie könnte sein, den gesamten Datenstrom zuerst direkt in eine Datei zu dumpen (ohne die Cap'n Proto-Bibliothek einzubeziehen), dann mmap() diese Datei und capnp::FlatArrayMessageReader zu verwenden, um die mmap() ed Daten direkt zu lesen.

Ich beschreibe all dies, weil es eine nette Sache ist, die mit Cap'n Proto möglich ist, aber nicht die meisten anderen Serialisierungsframeworks (z. B. konnte man das nicht mit Protobuf machen). Es ist manchmal sehr nützlich, mit mmap() Tricks zu spielen - ich habe das an mehreren Stellen in Sandstorm, Cap'n Proto's Elternprojekt, erfolgreich angewendet. Ich vermute jedoch, dass es für Ihren Anwendungsfall wahrscheinlich sinnvoller ist, das Dokument in eine Reihe von Nachrichten aufzuteilen.