Saltar a contenido

Última actualización:2026-10-12

Serialización ZeroCopy con Protobuf

1. ZeroCopyStream

protobuf tiene en sus interfaces de io algo llamado ZeroCopyStream; para el diseño de las interfaces de IO, pb ofrece las interfaces de serialización y deserialización correspondientes, como:

// Read a protocol buffer from the given zero-copy input stream.  If
// successful, the entire input will be consumed.
bool ParseFromZeroCopyStream(io::ZeroCopyInputStream* input);

// Write the message to the given zero-copy output stream.  All required
// fields must be set.
bool SerializeToZeroCopyStream(io::ZeroCopyOutputStream* output) const;

El propósito del diseño de ZeroCopyStream es minimizar el número de copias del buffer, es decir, omitir la copia de los datos internos del stream al buffer del usuario. Así, el stream puede devolver un búfer que en realidad apunta directamente a la estructura de datos final donde se almacenarán los bytes, y el llamador puede interactuar directamente con ese búfer, eliminando la operación de copia intermedia.

Por ejemplo: el clásico IO stream:

char buffer[[]BUFFER_SIZE];
input->Read(buffer, BUFFER_SIZE);
DoSomething(buffer, BUFFER_SIZE);

Después, el stream básicamente solo llama a memcpy() para copiar los datos de pb al buffer del usuario. Si usamos ZeroCopyInputStream, solo necesitamos:

const void* buffer;
int size;
input->Next(&buffer, &size);
DoSomething(buffer, size);

Aquí no se ejecuta ninguna copia: el llamador acaba leyendo directamente del buffer.

ZeroCopyStream proporciona dos clases base: ZeroCopyOutputStream/ZeroCopyInputStream.

Tomando InputStream como ejemplo, normalmente podemos personalizar nuestro propio ZeroCopyStream heredando de ella; hay que implementar las cuatro interfaces siguientes.

// implements ZeroCopyInputStream ----------------------------------
bool Next(const void** data, int* size);
void BackUp(int count);
bool Skip(int count);
int64 ByteCount() const;

Muchos frameworks rpc básicamente personalizan su propio Stream; por ejemplo: sofa-pbrpc

https://github.com/baidu/sofa-pbrpc/blob/master/src/sofa/pbrpc/buffer.h

2. Demo

Define un protocolo pb, por ejemplo: autenticación:

syntax = "proto3";

message BasicAuth {
  string username = 1;
  string password = 2;
}

A continuación escribe la serialización y la deserialización:

  • Serialización
BasicAuth auth_message;
auth_message.set_username("user123");
auth_message.set_password("password");

StringOutputStream output_stream(&buf);

auth_message.SerializeToZeroCopyStream(&output_stream);
  • Deserialización
ArrayInputStream input_stream(buf.data(), buf.size());
BasicAuth auth_message;
if (!auth_message.ParseFromZeroCopyStream(&input_stream)) {
    std::cerr << "Failed to parse data." << std::endl;
}

std::cout << "Username: " << auth_message.username() << std::endl;
std::cout << "Password: " << auth_message.password() << std::endl;

Después ya podemos llamarla:

int main() {
    std::string buf;
    buf.reserve(512);
    ser(buf);
    deser(buf);
    return 0;
}

Salida:

Username: user123
Password: password

Fin de esta sección; el código completo anterior está en la comunidad 知识星球; te invito a discutir conmigo temas relacionados con rpc.

(gdb) p *dsr
$8 = {typeInfo = {datumlen = 4, typid = 1082, align = 105 'i', byval = true}, datumStreamVersion = DatumStreamVersion_Original, nth = -1,
  logical_row_count = 16382, datump = 0x3840470 "\277\377\377\377\231\002", physical_datum_index = -1, physical_datum_count = 0,
  datum_beginp = 0x3840470 "\277\377\377\377\231\002", datum_afterp = 0x38503dc "", has_null = true, buffer_beginp = 0x383fc60 "",
  null_bitmap = {buffer = 0x383fc70 "", byteBit = 0 '\000', bytePointer = 0x383fc70 "", bitCount = 16382, bitPosition = -1,
    readBitOnCount = 58981488}, null_bitmap_beginp = 0x0, rle_can_have_compression = false, rle_compress_beginp = 0x0, rle_repeatcountsp = 0x0,
  delta_beginp = 0x0, delta_deltasp = 0x0, delta_datum_p = 0, rle_norepeats_null_bitmap_count = 0, rle_compress_bitmap_count = 0,
  rle_repeatcounts_count = 0, rle_repeatcounts_size = 0, delta_item = false, delta_bitmap_count = 0, deltas_count = 0, deltas_size = 0,
  rle_block_was_compressed = false, rle_compress_bitmap = {buffer = 0x0, byteBit = 0 '\000', bytePointer = 0x0, bitCount = 0, bitPosition = 0,
    readBitOnCount = 0}, rle_repeatcounts_index = 0, rle_in_repeated_item = false, rle_repeated_item_count = 0,
  rle_total_repeat_items_read = 0, delta_block_was_compressed = false, delta_bitmap = {buffer = 0x0, byteBit = 0 '\000',
    bytePointer = 0xff6c00454244 <error: Cannot access memory at address 0xff6c00454244>, bitCount = 0, bitPosition = 0,
    readBitOnCount = 13651600}, eyecatcher = "\000\000\000", physical_data_size = 0, maxDataBlockSize = 13652112,
  errdetailCallback = 0x35daeb0, errdetailArg = 0x34c4e18, errcontextCallback = 0x34c4e18, errcontextArg = 0x383fc60, memctxt = 0x1077c}

Temas relacionados

Comentarios