对象池存储固定大小的对象,针对多线程环境反复使用fixed size小对象的场景相比每次都new和delete有明显的性能提升。同时由于对象池不销毁对象的特性,极大简化了无锁编程中对象生命周期管理的问题。
源码位置:base/resource_pool.h base/resource_pool_inl.h base/object_pool.h base/object_pool_inl.h
bthread提供对象池ObjectPool和资源池ResourcePool,两者的差异仅仅在于ResourcePool额外提供了根据id获取指定对象的功能,因此这里针对ResourcePool进行分析,在不需要使用id功能的场景下可以直接使用ObjectPool,以减少维护id带来的不必要的开销。
数据结构
解释:
- ResourcePool<T>:每个用户类型T对应一个全局单例的ResourcePool,内部采用两级存储的方式存储T类型对象以节省空间
- group:第一级存储,每个group内部记录65535个block指针,一个ResourcePool最多存储65535个group
- block:第二级存储,每个block内部存储实际的T类型对象,数量根据T类型大小和用户配置决定,对于每个用户类型T,block内部存储的T类型对象数量是固定的
- _free_chunks:DynamicFreeChunk的数组,每个DynamicFreeChunk管理不定数量的空闲id
- LocalPool:每个thread在thread本地存储中管理一个LocalPool,所有和资源池交互的操作都通过LocalPool进行,这样可以利用缓存局部性以提升性能。LocalPool内部管理一个本地固定大小的FreeChunk,获取对象时先从FreeChunk中尝试,若本地FreeChunk为空会尝试从全局ResourcePool的_free_chunks中获取一个,若本地FreeChunk满,会弹到全局的_free_chunks中
- _cur_block:本地LocalPool管理的一个block,当没有free_id可供使用时,从这个block中分配对象
- id:对象在ResourcePool<T>中的全局唯一index,id = group_index * 65535 * block_index + offset_in_block
获取对象流程
1、从本地FreeChunk中获取free_id,若成功,根据id从ResoucePool<T>中获取T类型对象并根据参数决定该对象构造方式
2、若1失败,那么尝试从ResourcePool<T>中获取一个FreeChunk并copy到本地,然后重做1,这里注意copy是一个大开销操作但是这种操作很少,对整体摊销不大
3、若2失败,那么尝试从本地管理的Block中获取一个,计算id后返回,若本次没有Block或者已经满了,那么在ResourcePool<T>中创建一个新block,将此block设置为本地管理然后从中分配
4、若以上都失败了,那么分配失败,返回NULL
用户类型T构造和析构的说明
将T类型对象返回资源池不会对对象进行析构,从资源池中获取到的若是之前还给资源池的对象那么也不会执行构造。针对T中某些成员需要每次都初始化的,需要在资源池中拿到对象后自行做初始化处理