Ú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:
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:
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:
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:
Salida:
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¶
- Implementando C++ FnOnce a mano | C++那些事
- Pequeño proyecto gRPC en C++ | C++那些事
- Comunicación entre procesos en C++ en la práctica | C++那些事
- Demo de servicio de URL cortas en C++ | C++那些事
- C++17 y SQLite para medir el tamaño de directorios | C++那些事
- Pruebas de rendimiento con Google Benchmark | C++那些事
- Servidor HTTP con corrutinas de C++20 | C++那些事
- Acelerar Python con pybind11 | C++那些事
- Diseño de una calculadora de pila en C++ | C++那些事