Перейти к содержанию

Последнее обновление:2026-10-12

Сериализация Protobuf ZeroCopy

1. ZeroCopyStream

В io-интерфейсах protobuf есть так называемый ZeroCopyStream; для проектирования IO-интерфейсов pb предоставляет соответствующие интерфейсы сериализации и десериализации, например:

// 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;
ZeroCopyStream задумывался для минимизации числа копирований buffer, то есть для исключения копирования данных из внутреннего буфера stream в пользовательский buffer. Поэтому stream может вернуть буфер, который фактически напрямую указывает на итоговую структуру данных, где должны храниться байты, и вызывающий может взаимодействовать с этим буфером напрямую, устраняя промежуточное копирование.

Например, классический IO stream:

char buffer[[]BUFFER_SIZE];
input->Read(buffer, BUFFER_SIZE);
DoSomething(buffer, BUFFER_SIZE);
Затем stream, по сути, просто вызывает memcpy(), копируя данные из pb в пользовательский buffer. Если же использовать ZeroCopyInputStream, нам нужно лишь:

const void* buffer;
int size;
input->Next(&buffer, &size);
DoSomething(buffer, size);
Здесь копирования не происходит: вызывающий в итоге читает прямо из buffer.

ZeroCopyStream предоставляет два базовых класса: ZeroCopyOutputStream/ZeroCopyInputStream.

Возьмём InputStream: обычно мы можем наследованием определить собственный ZerCopyStream, для чего нужно реализовать следующие четыре интерфейса.

// implements ZeroCopyInputStream ----------------------------------
bool Next(const void** data, int* size);
void BackUp(int count);
bool Skip(int count);
int64 ByteCount() const;
Многие rpc-фреймворки практически всегда определяют собственные Stream, например: sofa-pbrpc

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

2. Demo

Определим pb-протокол, например: проверка авторизации:

syntax = "proto3";

message BasicAuth {
  string username = 1;
  string password = 2;
}
Затем напишем сериализацию и десериализацию:

  • Сериализация

BasicAuth auth_message;
auth_message.set_username("user123");
auth_message.set_password("password");

StringOutputStream output_stream(&buf);

auth_message.SerializeToZeroCopyStream(&output_stream);
- Десериализация

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;
После этого мы можем вызвать это:

int main() {
    std::string buf;
    buf.reserve(512);
    ser(buf);
    deser(buf);
    return 0;
}
Вывод:

Username: user123
Password: password
На этом раздел закончен; полный код выше — в закрытом сообществе; будем рады вместе обсудить темы, связанные с 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}

Похожие темы

Комментарии