iobuf:
block:
197 struct IOBuf::Block {
198 butil::atomic<int> nshared;
199 uint16_t flags;
200 uint16_t abi_check; // original cap, never be zero.
201 uint32_t size;
202 uint32_t cap;
203 Block* portal_next; //常规不触发,某些场景使用
207 char* data;
一个共享块,分配block后,持有之,这是一块大内存,头部是block元数据,后面是data,目前感觉data指针多余;通过nshared共享,该值内存顺序是memory_order_relaxed,只保证当前操作的原子性,不考虑线程间的同步,其他线程可能读到新值,也可能读到旧值。
构造iobuf,magic默认0,因此默认使用bigview:
86 struct BigView {
87 int32_t magic;
88 uint32_t start;//ref头
89 BlockRef* refs;
90 uint32_t nref;//ref个数
91 uint32_t cap_mask;
92 size_t nbytes;//总字节数
};
74 struct BlockRef {
75 // NOTICE: first bit of `offset' is shared with BigView::start
76 uint32_t offset;
77 uint32_t length;
78 Block* block;
79 };
refs是个数组,可用快速定位ref,每创建一个block都创建对应的BlockRef,存入refs;
这时候,iobuf整个结构应该也清晰了
append操作:
- 如果是1个字符调用push_back-》iobuf::share_tls_block()
- 这里获取g_tls_data,看下数据结构
-
318 struct TLSData { 319 // Head of the TLS block chain. 320 IOBuf::Block* block_head; 321 322 // Number of TLS blocks 323 int num_blocks; 324 325 // True if the remote_tls_block_chain is registered to the thread. 326 bool registered; 327 }; 328 329 static __thread TLSData g_tls_data = { NULL, 0, false };
- 线程独立的变量,获取head,如果没full、没NULL,通过Block的next指针遍历,如果full,减少引用计数
- 如果仍为NULL,创建新的block,num_blocks+1,覆盖block_head;
- 然后直接操作block的data,写入数据,查找refs中的位置,然后更新数据,增加nref值(表示ref个数)
- 常规使用,不会涉及tls,IOPortal和zerostream会使用
append是尾部操作,弹出有尾部弹出和头部弹出(pop_front,pop_back);
支持的特性比较多:
比如snappy压缩,ZeroCopyInputStream;
IOPortal支持fd读写,ssl读写,调用系统函数,readv/preadv;
IOBufAppender,没继承iobuf,append性能更好;
IOBuf之间交互,eg:拷贝构造,赋值,cut,append;基本做到零拷贝,通过Block引用计数,以及blockRef控制字节读取offset、length;
问题:
持续尾部写入,头部读取,导致start前移,不能循环使用,导致重新分配二倍空间;
想直接提取数据,cut,copy,fetch等操作会触发拷贝;不过确实,如果直接拿数据使用是存在风险的,通过IOBuf间接使用也是可以的;
使用场景:
正如doc所说,不适合程序内的通用结构;
适合IO,无修改的这种场景;