最終更新:2026-10-12
Protobuf ZeroCopy シリアライズ¶
1.ZeroCopyStream¶
protobuf の io インターフェースには 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:
そして、stream は基本的に memcpy() を呼び出してデータを pb からユーザーの buffer へコピーしているだけです。ZeroCopyInputStream を使えば、次のようにするだけです:
ここではコピーが実行されず、呼び出し元は最終的に buffer から直接読み出します。
ZeroCopyStream は 2 つの基底クラスを提供します:ZeroCopyOutputStream/ZeroCopyInputStream です。
InputStream を例にとると、通常は継承によって独自の ZerCopyStream を定義でき、次の 4 つのインターフェースを実装する必要があります。
// 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 プロトコルを 1 つ定義します。例えば、認可検証:
続いてシリアライズとデシリアライズを書きます:
- シリアライズ
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;
その後、これを呼び出すことができます:
出力:
この節はここまでです。以上の完全なコードは知識星球にあるので、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}
関連トピック¶
- C++ FnOnce を手書きする | C++那些事
- C++ gRPC ミニプロジェクト | C++那些事
- C++ プロセス間通信実践 | C++那些事
- C++ 短縮URLサービス Demo | C++那些事
- C++17 と SQLite でディレクトリサイズを集計 | C++那些事
- Google Benchmark 性能テスト | C++那些事
- C++20 コルーチン HTTP サーバ | C++那些事
- pybind11 で Python を高速化 | C++那些事
- C++ スタック型電卓の設計 | C++那些事