2016-01-16 16 views
5

consideri uno schema Cap'n'Proto come questo:stream durante la serializzazione con 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 */ 
} 

Ora voglio serializzare (vale a dire build) un'istanza del documento e streaming a una destinazione remota.

Poiché il documento è in genere molto grande, non desidero completamente crearlo in memoria prima di inviarlo. Invece sto cercando un costruttore che invia direttamente struct by struct over the wire. Tale che il buffer di memoria aggiuntivo necessario è costante (cioè O (max (sizeof (Header), sizeof (Record), sizeof (Footer)).

Guardando il materiale tutorial non trovo un tale costruttore. il MallocMessageBuilder sembra creare tutto in memoria prima (quindi si chiama writeMessageToFd su di esso).

supporta l'API Cap'n'Proto ad un caso d'uso?

O è Cap'n'Proto più significava da utilizzare per i messaggi che si adattano alla memoria prima dell'invio?

In questo esempio, la struttura del documento potrebbe essere omessa e quindi attivata e potrebbe semplicemente inviare una sequenza di un messaggio Header, n Record e un Footer. Dal momento che un messaggio di Cap'n'Proto è auto-delimitante, questo dovrebbe funzionare. Ma perdi la radice del documento, forse a volte questa non è davvero un'opzione.

risposta

7

La soluzione che hai delineato - l'invio delle parti del documento come messaggi separati - è probabilmente la migliore per il tuo caso d'uso. Fondamentalmente, Cap'n Proto non è progettato per lo streaming di blocchi di un singolo messaggio, dal momento che non si adatta bene alle sue proprietà di accesso casuale (ad esempio cosa succede quando si tenta di seguire un puntatore che punta a un chunk che non hai ricevuto ancora?). Invece, quando si desidera lo streaming, è necessario suddividere un messaggio di grandi dimensioni in una serie di messaggi più piccoli.

Detto questo, a differenza di altri sistemi simili (ad esempio Protobuf), Cap'n Proto non richiede strettamente i messaggi per adattarsi alla memoria. Nello specifico, puoi fare alcuni trucchi usando mmap(2). Se i dati del documento provengono da un file su disco, è possibile memorizzare il file in mmap() e quindi incorporarlo nel messaggio. Con mmap(), il sistema operativo in realtà non legge i dati dal disco finché non si tenta di accedere alla memoria e il sistema operativo può anche eliminare le pagine dalla memoria dopo l'accesso poiché sa che ha ancora una copia sul disco. Questo spesso ti permette di scrivere codice molto più semplice, dal momento che non hai più bisogno di pensare alla gestione della memoria.

Per incorporare un chunk di mmap() in un messaggio Cap'n Proto, è necessario utilizzare capnp::Orphanage::referenceExternalData(). Ad esempio, dato:

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

si potrebbe scrivere:

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

Perché Capitano Proto è zero-copy, finirà per scrivere la memoria mmap() ndr direttamente alla presa senza mai accesso esso. Spetta quindi al sistema operativo leggere il contenuto dal disco e inserirlo nel socket in modo appropriato.

Ovviamente, hai ancora un problema sul lato ricevente. Troverai molto più difficile progettare il destinatario per leggere nella memoria di mmap() edita. Una strategia potrebbe essere quella di scaricare l'intero flusso direttamente in un file per primo (senza coinvolgere la libreria Cap'nProto), quindi il file mmap() e utilizzare capnp::FlatArrayMessageReader per leggere i dati di mmap() sul posto.

Descrivo tutto questo perché è una cosa carina che è possibile con Cap'n Proto ma non con la maggior parte degli altri framework di serializzazione (ad esempio, non è possibile farlo con Protobuf). Giocare con mmap() a volte è davvero utile: l'ho usato con successo in diversi punti nel Sandstorm, il progetto padre di Cap'n Proto. Tuttavia, sospetto che per il tuo caso d'uso, la suddivisione del documento in una serie di messaggi abbia probabilmente più senso.

Problemi correlati