brpc学习笔记(二)- 资源池ResourcePool及其相关

ResourceId

定义

struct ResourceId {
    uint64_t value;

    operator uint64_t() const {
        return value;
    }

    template <typename T2>
    ResourceId<T2> cast() const {
        ResourceId<T2> id = { value };
        return id;
    }
};

实际上就是一个uint64_t。
ResourceId是resource pool中某个资源的唯一标识,所有的资源获取和归还都是基于ResourceId的

ResourcePoolFreeChunk

定义

template <typename T, size_t NITEM> 
struct ResourcePoolFreeChunk {
    size_t nfree;
    ResourceId<T> ids[NITEM];
};
// for gcc 3.4.5
template <typename T> 
struct ResourcePoolFreeChunk<T, 0> {
    size_t nfree;
    ResourceId<T> ids[0];
};



// class ResourcePool中声明
typedef ResourcePoolFreeChunk<T, FREE_CHUNK_NITEM>      FreeChunk;
typedef ResourcePoolFreeChunk<T, 0> DynamicFreeChunk;

模板传入两个参数,类型T和长度NITEM。当NITEM=0时,成员变量ids为柔性数组。

成员变量中,ids存储若干个ResourceId,元素个数为nfree。

顾名思义,FreeChunk即存储空闲的Chunk(块),ids中的即为空闲块。chunk实际上就是被分配出去又归还回来的resouceid集合。

ResourcePool

ResourcePool其实可以分为两个模块,一个是实际存放资源的内存模块(Block, BlockGroup),一个是存放归还回来空间对应ResourceId的数据结构(ResourcePoolFreeChunk)。ResourcePool使用thread local的LocalPool来管理当前线程的资源分配,尽量减少数据竞争。但LocalPool内资源用尽/初始无资源还是由ResourcePool进行分配。分配资源的基本原则是,先从回收的空闲资源分配,再从内存模块中分配。

成员变量

一些重要的成员变量

    static BAIDU_THREAD_LOCAL LocalPool* _local_pool;	// 每个线程有一个
	static butil::static_atomic<long> _nlocal;  // local pool数量
	static butil::static_atomic<size_t> _ngroup; // 当前使用的block_group数量
	static butil::static_atomic<BlockGroup*> _block_groups[RP_MAX_BLOCK_NGROUP];	// RP_MAX_BLOCK_NGROUP=65536,上限内存65536*65536*65536B,上限资源个数65536*65536*256。
	std::vector<DynamicFreeChunk*> _free_chunks;	// 用来缓存已经分配出去又回收的ResourceId。相当于是一个二维的resourceId*数组,DynamicFreeChunk是变长的。

_block_groups初始仅创建BlockGroup类型数组,并没有真正申请内存。当需要用到的时候Block的时候,再去申请BlockGroup空间,此时也是仅分配了Block数组的空间;再申请Block空间,才是真正分配了资源空间。 加上资源可以回收再分配,很难全用完。

但这里有个问题,当某个资源用完了返回给resourcePool的时候,是放在_free_chunks中的,并不会delete让系统回收,所以如果需要短时间分配大量资源,用完之后进程占用的系统内存是不会降回来的。该问题在github上也有反馈

注意_free_chunks使用需要注意线程间竞争问题,但_free_chunks中的单个DynamicFreeChunk仅会被单个线程使用。

以上变量主要属于以下内部类

内部类

Block

	template <typename T> struct ResourcePoolBlockMaxSize {
	    static const size_t value = 64 * 1024; // bytes
	};
	template <typename T> struct ResourcePoolBlockMaxItem {
	    static const size_t value = 256;
	};
	class ResourcePoolBlockItemNum {
	    static const size_t N1 = ResourcePoolBlockMaxSize<T>::value / sizeof(T);
	    static const size_t N2 = (N1 < 1 ? 1 : N1);
	public:
	    static const size_t value = (N2 > ResourcePoolBlockMaxItem<T>::value ?	// 最大256
	                                 ResourcePoolBlockMaxItem<T>::value : N2);
	};
	static const size_t BLOCK_NITEM = ResourcePoolBlockItemNum<T>::value;	// 如果sizeof(T)>256,BLOCK_NITEM <256,BLOCK_NITEM*sizeof(T)=65536
	struct BAIDU_CACHELINE_ALIGNMENT Block {
        char items[sizeof(T) * BLOCK_NITEM];
        size_t nitem;

        Block() : nitem(0) {}
    };

	// 生成Block,ResourcePool成员函数
	// Create a Block and append it to right-most BlockGroup.
    static Block* add_block(size_t* index) {
        Block* const new_block = new(std::nothrow) Block;	// 申请Block资源
        if (NULL == new_block) {
            return NULL;
        }

        size_t ngroup;
        do {
            ngroup = _ngroup.load(butil::memory_order_acquire);
            if (ngroup >= 1) {
                BlockGroup* const g =
                    _block_groups[ngroup - 1].load(butil::memory_order_consume);
                const size_t block_index =
                    g->nblock.fetch_add(1, butil::memory_order_relaxed);
                if (block_index < RP_GROUP_NBLOCK) {
                    g->blocks[block_index].store(
                        new_block, butil::memory_order_release);	// 将新生成的block存入blockGroup中
                    *index = (ngroup - 1) * RP_GROUP_NBLOCK + block_index;	// 返回下标逻辑是将二维下标转成一维
                    return new_block;	// 返回给localpool使用
                }
                g->nblock.fetch_sub(1, butil::memory_order_relaxed);
            }
        } while (add_block_group(ngroup));	// 当初始_block_groups数组中只有空指针时,申请一个BlockGroup资源

        // Fail to add_block_group.
        delete new_block;
        return NULL;
    }

这里的Block是ResourcePool的内部类,和上期讲的Block虽然不是一个东西,但是用途类似。实际上是内存块,大小一般是65536B,ResourcePool的数据都是放在Block中的。Block中有一个items用于存储实际数据,nitem表示中的bytes数。
一般情况下,brpc中使用到ResourcePool的类大小都会>256B,所以一般情况下一个Block大小是65536B。
Block是非线程安全的,只能在单个线程中操作。

BlockGroup

	static const size_t RP_GROUP_NBLOCK_NBIT = 16;
	static const size_t RP_GROUP_NBLOCK = (1UL << RP_GROUP_NBLOCK_NBIT); // 65536
	struct BlockGroup {
        butil::atomic<size_t> nblock;
        butil::atomic<Block*> blocks[RP_GROUP_NBLOCK];	// 共65536个Block,一共65536*65536B=4G

        BlockGroup() : nblock(0) {
            // We fetch_add nblock in add_block() before setting the entry,
            // thus address_resource() may sees the unset entry. Initialize
            // all entries to NULL makes such address_resource() return NULL.
            memset(blocks, 0, sizeof(butil::atomic<Block*>) * RP_GROUP_NBLOCK);
        }
    };
	
	// 生成BlockGroup,ResourcePool成员函数
	// Create a BlockGroup and append it to _block_groups.
    // Shall be called infrequently because a BlockGroup is pretty big.
    static bool add_block_group(size_t old_ngroup) {
        BlockGroup* bg = NULL;
        BAIDU_SCOPED_LOCK(_block_group_mutex);
        const size_t ngroup = _ngroup.load(butil::memory_order_acquire);
        if (ngroup != old_ngroup) {
            // Other thread got lock and added group before this thread.
            return true;
        }
        if (ngroup < RP_MAX_BLOCK_NGROUP) {
            bg = new(std::nothrow) BlockGroup;
            if (NULL != bg) {
                // Release fence is paired with consume fence in address() and
                // add_block() to avoid un-constructed bg to be seen by other
                // threads.
                _block_groups[ngroup].store(bg, butil::memory_order_release);
                _ngroup.store(ngroup + 1, butil::memory_order_release);
            }
        }
        return bg != NULL;
    }

ResourcePool内部类,内有一个blocks数组,以及nblock标识block数量。一个Block数组,用于管理Block(实际上BlockGroup数组相当于生成一个二维的Block*数组?)。
BlockGroup是线程安全的,一个BlockGroup中的各个Block,可能给不同线程使用.

LocalPool

ResourcePool内部类。在一个进程下,每个模板T中的所有ResourcePool对象(实际只有一个),在每个线程中,共用一个LocalPool。每个线程只会从自己的LocalPool中获取数据,减少数据竞争。

// Each thread has an instance of this class.
    class BAIDU_CACHELINE_ALIGNMENT LocalPool {
    private:
        ResourcePool* _pool;	// LocalPool所属的ResourcePool,在ResourcePool中生成LocalPool时会把this传进来
        Block* _cur_block;		// 如果_cur_free没有可用资源,则从_cur_block中按顺序分配
        size_t _cur_block_index;	// _cur_block在所属的ResourcePool的_block_groups中的下标。_cur_block = _block_groups[i][j], _cur_block_index=i*RP_GROUP_NBLOCK+j
        FreeChunk _cur_free;	// 缓存空闲的resourceId资源,主要是一些已经分配出去了的又返还回来的零碎resourceId资源。在分配资源时优先使用这些资源。
    };
    static BAIDU_THREAD_LOCAL LocalPool* _local_pool;	// ResourcePool中对LocalPool的声明,静态变量+thread local

	// 生成localPool,ResourcePool成员函数
	inline LocalPool* get_or_new_local_pool() {
        LocalPool* lp = _local_pool;
        if (lp != NULL) {
            return lp;
        }
        lp = new(std::nothrow) LocalPool(this);
        if (NULL == lp) {
            return NULL;
        }
        BAIDU_SCOPED_LOCK(_change_thread_mutex); //avoid race with clear()
        _local_pool = lp;
        butil::thread_atexit(LocalPool::delete_local_pool, lp);
        _nlocal.fetch_add(1, butil::memory_order_relaxed);
        return lp;
    }

LocalPool管理当前线程的资源分配,获取资源的get_resource函数,实际也是调用LocalPool的get函数。

LocalPool成员函数:

  1. get

    	// We need following macro to construct T with different CTOR_ARGS
        // which may include parenthesis because when T is POD, "new T()"
        // and "new T" are different: former one sets all fields to 0 which
        // we don't want.
    #define BAIDU_RESOURCE_POOL_GET(CTOR_ARGS)                              \
        /* Fetch local free id */                                       \
        if (_cur_free.nfree) {                                          \
            const ResourceId<T> free_id = _cur_free.ids[--_cur_free.nfree]; \
            *id = free_id;                                              \
            BAIDU_RESOURCE_POOL_FREE_ITEM_NUM_SUB1;                   \
            return unsafe_address_resource(free_id);                    \
        }                                                               \
        /* Fetch a FreeChunk from global.                               \
           TODO: Popping from _free needs to copy a FreeChunk which is  \
           costly, but hardly impacts amortized performance. */         \
        if (_pool->pop_free_chunk(_cur_free)) {                         \
            --_cur_free.nfree;                                          \
            const ResourceId<T> free_id =  _cur_free.ids[_cur_free.nfree]; \
            *id = free_id;                                              \
            BAIDU_RESOURCE_POOL_FREE_ITEM_NUM_SUB1;                   \
            return unsafe_address_resource(free_id);                    \
        }                                                               \
        /* Fetch memory from local block */                             \
        if (_cur_block && _cur_block->nitem < BLOCK_NITEM) {            \
            id->value = _cur_block_index * BLOCK_NITEM + _cur_block->nitem; \
            T* p = new ((T*)_cur_block->items + _cur_block->nitem) T CTOR_ARGS; \
            if (!ResourcePoolValidator<T>::validate(p)) {               \
                p->~T();                                                \
                return NULL;                                            \
            }                                                           \
            ++_cur_block->nitem;                                        \
            return p;                                                   \
        }                                                               \
        /* Fetch a Block from global */                                 \
        _cur_block = add_block(&_cur_block_index);                      \
        if (_cur_block != NULL) {                                       \
            id->value = _cur_block_index * BLOCK_NITEM + _cur_block->nitem; \
            T* p = new ((T*)_cur_block->items + _cur_block->nitem) T CTOR_ARGS; \
            if (!ResourcePoolValidator<T>::validate(p)) {               \
                p->~T();                                                \
                return NULL;                                            \
            }                                                           \
            ++_cur_block->nitem;                                        \
            return p;                                                   \
        }                                                               \
        return NULL;                                                    \
    	inline T* get(ResourceId<T>* id) {
            BAIDU_RESOURCE_POOL_GET();
        }
    
        template <typename A1>
        inline T* get(ResourceId<T>* id, const A1& a1) {
            BAIDU_RESOURCE_POOL_GET((a1));
        }
    
        template <typename A1, typename A2>
        inline T* get(ResourceId<T>* id, const A1& a1, const A2& a2) {
            BAIDU_RESOURCE_POOL_GET((a1, a2));
        }
    

    获取资源、分配resourceId的函数。宏BAIDU_RESOURCE_POOL_GET的注释提到,new T()new T是不一样的,前者会对所有字段初始化为0,后者不会,brpc中大部分使用的都是后者。
    函数处理逻辑如下:
    1. 调用get时,首先判断_cur_free中是否还有空闲的Chunk,有即从_cur_free数组末端取出空闲位置的ResourceId,并将该ResourceId从_cur_free.ids去掉。获取到空闲位置的ResourceId后,调用unsafe_address_resource从内存块中找到ResourceId对应的地址,则这块大小为T的内存就分配出来供业务使用了。unsafe_address_resource逻辑后面单独写。
    2. 如果没有从_cur_free中获取到空闲的resouceid,则再尝试从ResourcePool中的_free_chunks中pop出一个FreeChunk来,作为当前的_cur_free,再从_cur_free中取,和1一样。
    3. 如果当前没有空闲的chunk了,但当前_cur_block不为空,并且_cur_block的元素没有超上限(65536/sizeof(T)),则从cur_block中末尾向后占用sizeof(T)大小的空间,分配给当前申请的资源,更新_cur_block的占用情况。找到存放资源的内存位置之后,就可以根据它在block中的位置生成ResourceId,生成资源并返回。生成ResourceId的逻辑在介绍ResourceId的位置细讲。
    4. _cur_block为空或_cur_block里的元素超过上限了,则需要向ResourcePool申请一个新的block作为_cur_block,申请逻辑放在add_block()函数中讲。有了可用的_cur_block后就可以按照步骤3分配资源并返回。
    5. 前面说到,假如_cur_block = _block_groups[i][j], 则_cur_block_index=iRP_GROUP_NBLOCK+j,那么resourceId=_cur_block_indexBLOCK_NITEM +_cur_block->nitem,即resourceId的生成规则

  2. return_resource

    	inline int return_resource(ResourceId<T> id) {
            // Return to local free list
            if (_cur_free.nfree < ResourcePool::free_chunk_nitem()) {
                _cur_free.ids[_cur_free.nfree++] = id;
                BAIDU_RESOURCE_POOL_FREE_ITEM_NUM_ADD1;
                return 0;
            }
            // Local free list is full, return it to global.
            // For copying issue, check comment in upper get()
            if (_pool->push_free_chunk(_cur_free)) {
                _cur_free.nfree = 1;
                _cur_free.ids[0] = id;
                BAIDU_RESOURCE_POOL_FREE_ITEM_NUM_ADD1;
                return 0;
            }
            return -1;
        }
    

    如果_cur_free未满,就归还到_cur_free中。_cur_free满了则先将整个_cur_free归还回ResourcePool中的_free_chunks,再将id归还到 _cur_free中。

成员函数及ResourcePool相关函数

  1. get_resource

    inline T* get_resource(ResourceId<T>* id) {
        LocalPool* lp = get_or_new_local_pool();
        if (__builtin_expect(lp != NULL, 1)) {
            return lp->get(id);
        }
        return NULL;
    }
    
    template <typename A1>
    inline T* get_resource(ResourceId<T>* id, const A1& arg1) {
        LocalPool* lp = get_or_new_local_pool();
        if (__builtin_expect(lp != NULL, 1)) {
            return lp->get(id, arg1);
        }
        return NULL;
    }
    
    template <typename A1, typename A2>
    inline T* get_resource(ResourceId<T>* id, const A1& arg1, const A2& arg2) {
        LocalPool* lp = get_or_new_local_pool();
        if (__builtin_expect(lp != NULL, 1)) {
            return lp->get(id, arg1, arg2);
        }
        return NULL;
    }
    
  2. return_resource

    inline int return_resource(ResourceId<T> id) {
        LocalPool* lp = get_or_new_local_pool();
        if (__builtin_expect(lp != NULL, 1)) {
            return lp->return_resource(id);
        }
        return -1;
    }
    

    都很眼熟,前面LocalPool部分阐述过具体逻辑。

  3. address_resource

    static inline T* address_resource(ResourceId<T> id) {
        const size_t block_index = id.value / BLOCK_NITEM;
        const size_t group_index = (block_index >> RP_GROUP_NBLOCK_NBIT);
        if (__builtin_expect(group_index < RP_MAX_BLOCK_NGROUP, 1)) {
            BlockGroup* bg =
                _block_groups[group_index].load(butil::memory_order_consume);
            if (__builtin_expect(bg != NULL, 1)) {
                Block* b = bg->blocks[block_index & (RP_GROUP_NBLOCK - 1)]
                           .load(butil::memory_order_consume);
                if (__builtin_expect(b != NULL, 1)) {
                    const size_t offset = id.value - block_index * BLOCK_NITEM;
                    if (__builtin_expect(offset < b->nitem, 1)) {
                        return (T*)b->items + offset;
                    }
                }
            }
        }
    
        return NULL;
    }
    

    前面说过id的生成规则,那么反向操作即可找到对应的Block以及地址偏移量,即可取到对应的地址。

  4. pop_free_chunk/push_free_chunk

    bool pop_free_chunk(FreeChunk& c) {
        // Critical for the case that most return_object are called in
        // different threads of get_object.
        if (_free_chunks.empty()) {
            return false;
        }
        pthread_mutex_lock(&_free_chunks_mutex);
        if (_free_chunks.empty()) {
            pthread_mutex_unlock(&_free_chunks_mutex);
            return false;
        }
        DynamicFreeChunk* p = _free_chunks.back();
        _free_chunks.pop_back();
        pthread_mutex_unlock(&_free_chunks_mutex);
        c.nfree = p->nfree;
        memcpy(c.ids, p->ids, sizeof(*p->ids) * p->nfree);
        free(p);
        return true;
    }
    
    bool push_free_chunk(const FreeChunk& c) {
        DynamicFreeChunk* p = (DynamicFreeChunk*)malloc(    // 申请一个DynamicFreeChunk 大小与c一致
            offsetof(DynamicFreeChunk, ids) + sizeof(*c.ids) * c.nfree);    // offsetof:ids在DynamicFreeChunk中的偏移量
        if (!p) {
            return false;
        }
        p->nfree = c.nfree;
        memcpy(p->ids, c.ids, sizeof(*c.ids) * c.nfree);
        pthread_mutex_lock(&_free_chunks_mutex);
        _free_chunks.push_back(p);
        pthread_mutex_unlock(&_free_chunks_mutex);
        return true;
    }
    

    pop_free_chunk拿到_free_chunks中最后一个DynamicFreeChunk,复制到FreeChunk中即可使用。
    push_free_chunk则申请一个DynamicFreeChunk,大小与需要存储的FreeChunk c一致,并将c拷贝到DynamicFreeChunk中,加到_free_chunks中。
    注意_free_chunks的操作都要加锁。

了解了以上内部类之后,我们对ResourcePool可以有个基本的认识。ResourcePool由若干个BlockGroup组成,BlockGroup由若干个Block组成,在一个Block上,最多可以分配256个对象。用户可以通过ResourceId来从具体的Block上找到对应对象的地址。为了做到资源的回收利用,ResourcePool还包括一个用来存储ResourceId列表的数据结构,即DynamicFreeChunk(FreeChunk)列表。因为ResourcePool是一个单例,所有进程共用一个ResourcePool。为了减少进程间的竞争,还引入了LocalPool。每个LocalPool包含一个Block和一个FreeChunk,FreeChunk可以回收并重新利用资源,Block可以直接下发资源。当LocalPool的资源用尽,将重新去所属ResourcePool中申请FreeChunk或Block。

ObjectPool

ObjectPool实际上是ResourcePool的变种,也有Group、TaskGroup、FreeChunk等结构,唯一不同的是生成的对象不会返回一个ResourceId,仅返回对象指针。看具体需要决定使用哪种类型。比如后面说到的TaskMeta使用的是resourcePool,TaskMeta中的栈空间使用的是ObjectPool

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值