Add SingleIOBuf for efficient flatbuffers serialization/deserialization#3062
Add SingleIOBuf for efficient flatbuffers serialization/deserialization#3062chenBright merged 3 commits intoapache:masterfrom
Conversation
There was a problem hiding this comment.
Pull Request Overview
This PR introduces a new SingleIOBuf class designed to provide efficient flatbuffers serialization/deserialization by maintaining data in a contiguous single memory block, unlike the existing IOBuf which can use multiple blocks.
- Adds
SingleIOBufclass with contiguous memory management for flatbuffers integration - Implements memory allocation, deallocation, and reallocation methods specific to single-block operations
- Provides conversion methods between
SingleIOBufandIOBuffor interoperability
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| src/butil/iobuf.h | Declares the new SingleIOBuf class with public interface and friend declarations |
| src/butil/iobuf.cpp | Implements all SingleIOBuf methods including memory management and data operations |
| test/iobuf_unittest.cpp | Adds comprehensive unit tests covering SingleIOBuf functionality |
src/butil/iobuf.cpp
Outdated
| uint32_t in_use_front) { | ||
| IOBuf::BlockRef& ref = _cur_ref; | ||
| if (BAIDU_UNLIKELY(new_size <= ref.length)) { | ||
| LOG(ERROR) << "invaild new size:" << new_size; |
There was a problem hiding this comment.
Typo in error message: 'invaild' should be 'invalid'.
| LOG(ERROR) << "invaild new size:" << new_size; | |
| LOG(ERROR) << "invalid new size:" << new_size; |
src/butil/iobuf.cpp
Outdated
| _cur_ref.block->inc_ref(); | ||
| return true; | ||
| } | ||
| return false; |
There was a problem hiding this comment.
This return statement is unreachable code. The function will always return from either the true or false branch of the preceding if-else statement.
| return false; |
|
|
||
| TEST_F(IOBufTest, single_iobuf) { | ||
| butil::IOBuf buf1; | ||
| char *usr_str = (char *)malloc(16); |
There was a problem hiding this comment.
Memory allocated with malloc() is not freed in this test, causing a memory leak. Consider using RAII or explicitly calling free().
src/butil/iobuf.cpp
Outdated
|
|
||
| void SingleIOBuf::deallocate(void* p) { | ||
| if (_cur_ref.block) { | ||
| if (_cur_ref.block->data + _cur_ref.offset == (char*)p) { |
There was a problem hiding this comment.
[nitpick] The deallocate method only resets if the pointer matches exactly the beginning of the allocated region. This seems overly restrictive and may not handle all valid deallocation scenarios correctly.
| if (_cur_ref.block->data + _cur_ref.offset == (char*)p) { | |
| char* start = _cur_ref.block->data + _cur_ref.offset; | |
| char* end = start + _cur_ref.length; | |
| char* ptr = static_cast<char*>(p); | |
| if (ptr >= start && ptr < end) { |
|
Very useful feature for me! |
src/butil/iobuf.cpp
Outdated
| return false; | ||
| } | ||
|
|
||
| void SingleIOBuf::append_to(IOBuf* buf) const { |
There was a problem hiding this comment.
In RPC scenarios, data length is dynamic, while SingleIOBuf typically manages a fixed-size memory block. would you please explain how to use it in PRC parse message and pack message?
for example, when receive a message we don't know the length of this message, how to allocate memory, if memory is short, we need to reallocate and need memcopy?
There was a problem hiding this comment.
I'm also curious about the implementation here.
There was a problem hiding this comment.
在flatbuffers中用户使用fbs文件定义message格式(类似于proto)。所以在用户应用程序中创建请求时可以按消息大小分配内存。构造出的消息内容保存在SingleIOBuf中,最终它会通过SingleIOBuf::append_to添加到IOBuf中发送出去。brpc接收到的消息同样保存在IOBuf中。因此在解析请求时,将调用SingleIOBuf::assign将IOBuf assign给SingleIOBuf, 如果IOBuf的单个Block能容纳整个message,则直接将该Block赋值给SingleIOBuf,否则会根据消息大小重新申请足够大的Block,再从传入的IOBuf中拷贝数据。
RPC message的Serialize与Deserialize过程将在flatbuffers中实现。
There was a problem hiding this comment.
所以接收消息这块可能会有拷贝,这块最佳实践是评估请求大小,最好是一个Block能容纳得下。
There was a problem hiding this comment.
所以接收消息这块可能会有拷贝,这块最佳实践是评估请求大小,最好是一个Block能容纳得下。
是的,当前单个Block的默认大小是8KB(由DEFAULT_BLOCK_SIZE指定)。
Co-authored-by: lishuo02<lishuo02@baidu.com> Co-authored-by: xulei25 <xulei25@baidu.com>
1610d89 to
a271976
Compare
| SingleIOBuf& SingleIOBuf::operator=(const SingleIOBuf& rhs) { | ||
| reset(); | ||
| _block_size = 0; | ||
| if (rhs._cur_ref.block != NULL) { |
There was a problem hiding this comment.
Is it reasonable to set _block_size=0 after assignment? use swap function?
There was a problem hiding this comment.
Yes, _block_size is used to determine whether the block should be released.
src/butil/single_iobuf.h
Outdated
| IOBuf::Block* alloc_block_by_size(uint32_t data_size); | ||
|
|
||
| private: | ||
| // Current block the SingleIOBuf used. |
There was a problem hiding this comment.
is this only one block in SingleIOBuf?
There was a problem hiding this comment.
Yes, That's why it is called "Single"
|
|
||
| int SingleIOBuf::assign_user_data(void* data, size_t size, void (*deleter)(void*)) { | ||
| reset(); | ||
| if (size > 0xFFFFFFFFULL - 100) { |
There was a problem hiding this comment.
What is the exact meaning of 100? Does it refer to the reserved area of in_use_back?
There was a problem hiding this comment.
The threshold 0xFFFFFFFFULL - 100 is chosen to stay consistent with the implementation of IOBuf::append_user_data_with_meta().
src/butil/iobuf.cpp
Outdated
| } | ||
|
|
||
| inline IOBuf::Block* create_block(const size_t block_size) { | ||
| IOBuf::Block* create_block(const size_t block_size) { |
There was a problem hiding this comment.
remove the inline keyword may affect performance, you can move the function body to iobuf_inl.h, since that file is for inline functions.
0ef1c7d to
4c148bf
Compare
|
LGTM |
|
Add Flatbuffers-based RPC handling policy to apache/brpc. |
是的,会实现一个新的协议。 |
| } else { | ||
| IOBuf::Block* b = alloc_block_by_size(msg_size); | ||
| if (!b) { | ||
| return false; |
There was a problem hiding this comment.
Is it better to ensure that the block is allocated successfully before reset?
There was a problem hiding this comment.
Yes, it's meaningful. The latest commit has included this.
src/butil/single_iobuf.cpp
Outdated
| } | ||
| } | ||
|
|
||
| int SingleIOBuf::assign_user_data(void* data, size_t size, void (*deleter)(void*)) { |
There was a problem hiding this comment.
Like IOBuf, the deleter type should be std::function, not a function pointer.
Line 250 in f949962
| // Reallocates the current buffer to a larger size. | ||
| void* reallocate_downward(uint32_t new_size, | ||
| uint32_t in_use_back, | ||
| uint32_t in_use_front); |
There was a problem hiding this comment.
Add a comment to explain in_use_back and in_use_front.
Co-authored-by: lishuo02<lishuo02@baidu.com> Co-authored-by: xulei25 <xulei25@baidu.com> Co-authored-by: qiqingbo<qiqingbo@baidu.com>
4c148bf to
3a7eb33
Compare
|
LGTM |
|
@Q1ngbo 挺感兴趣brpc引入flatbuffers特性的,这块后续的计划是什么?目前还没看到step2和step3的pr |
现在有啦 |
What problem does this PR solve?
Issue Number:
#2354 #978
Problem Summary:
Hi, We plan to migrate the flatbuffers protocol used internally by Baidu to the community, which is expected to be divided into the following three steps:
These steps have a sequential dependency, and this commit completes the first step.
Flatbuffers requires that data to be serialized/deserialized be stored in a contiguous memory buffer. To meet this requirement, we designed SingleIOBuf. Unlike brpc’s existing IOBuf, SingleIOBuf uses a single Block to hold contiguous data, which better aligns with Flatbuffers' memory layout requirements. We also defined corresponding memory allocation and release interfaces to support efficient management of SingleIOBuf.
What is changed and the side effects?
Changed:
Side effects:
Performance effects:
Breaking backward compatibility:
Check List: