Central Cache ——承上启下的重要结构

高并发内存池--central cache

central cache也是一个哈希桶结构,他的哈希桶的映射关系跟thread cache是一样的。不同的是他的每 个哈希桶位置挂是SpanList链表结构,不过每个映射桶下面的span中的大内存块被按映射关系切成了一 个个小内存块对象挂在span的自由链表中

前置知识:

  1. central cache的每个哈希桶挂着spanlist的链表结构,每一个节点是一个span,span是以页为单位的大内存,被按照和thread cache一样的映射关系切成了一个个小内存,若thread1和thread2同时来central cache找1号桶,那么先获得桶锁的先访问。我会去把span 切给你,若没有就去page申请,并且切好。

  1. 一页假设为8k,就是2^13,以页为单位释放?

  1. usecount++用了 usecount--还了,当usecount减为0说明可以归还给page,page cache合并前后页形成更大的内存,解决内存碎片”外碎片“问题

  1. span可能是一页或者多页的跨度,被切成小对象,批量给thread cache,但是到底哪个span有空间是不确定的,spanlist是双向链表

  1. central cache是全局的,所有线程共享,因此设计单例模式,采用饿汉模式

哈希桶的每个位置下面挂的都是Span对象链接的链表,不同的是

1、8Byte映射位置下面挂的span中的页被切成8Byte大小的对象的自由链表。

2、256KB位置的span中的页被切成256KB大小对象的自由链表

  1. 申请内存: 1. 当thread cache中没有内存时,就会批量向central cache申请一些内存对象,这里的批量获取对 象的数量使用了类似网络tcp协议拥塞控制的慢开始算法;central cache也有一个哈希映射的 spanlist,spanlist中挂着span,从span中取出对象给thread cache,这个过程是需要加锁的,不 过这里使用的是一个桶锁,尽可能提高效率。 2. central cache映射的spanlist中所有span的都没有内存以后,则需要向page cache申请一个新的 span对象,拿到span以后将span管理的内存按大小切好作为自由链表链接到一起。然后从span 中取对象给thread cache。 3. central cache的中挂的span中use_count记录分配了多少个对象出去,分配一个对象给thread cache,就++use_count

  1. 释放内存

1. 当thread_cache过长或者线程销毁,则会将内存释放回central cache中的,释放回来时-- use_count。当use_count减到0时则表示所有对象都回到了span,则将span释放回page cache, page cache中会对前后相邻的空闲页进行合并。

项目分解

  1. span

定义一个span节点的结构,span是双向链表的节点

// 管理多个连续页大块内存跨度结构
struct Span
{
    PAGE_ID _pageId = 0; // 大块内存起始页的页号
    size_t  _n = 0;      // 页的数量

    Span* _next = nullptr;    // 双向链表的结构
    Span* _prev = nullptr;

    size_t _useCount = 0; // 切好小块内存,被分配给thread cache的计数
    void* _freeList = nullptr;  // 切好的小块内存的自由链表
};

如果是32位程序,那么虚拟地址空间的总容量位2^32,如果是64位程序,那么虚拟地址空间总容量位2^64,因此页号的类型也是不一样的,需要采取条件编译

#ifdef _WIN64
    typedef unsigned long long PAGE_ID;
#elif _WIN32
    typedef size_t PAGE_ID;
#else
    // linux
#endif
  1. spanList

central cache 哈希桶挂接的链表结构

// 带头双向循环链表 
class SpanList
{
public:
    SpanList()
    {
        _head = new Span;
        _head->_next = _head;
        _head->_prev = _head;
    }//span的构造函数,初始化位单节点的双向循环链表

    //将一个span节点插入spanlist中,相当于是从page申请过来的?
    void Insert(Span* pos, Span* newSpan)
    {
        assert(pos);
        assert(newSpan);

        Span* prev = pos->_prev;
        // prev newspan pos
        prev->_next = newSpan;
        newSpan->_prev = prev;
        newSpan->_next = pos;
        pos->_prev = newSpan;
    }

    //删除一个节点,只是去掉在spanlist中的链接,并不需要delete,因为后续要归还给page?
    void Erase(Span* pos)
    {
        assert(pos);
        assert(pos != _head);

        Span* prev = pos->_prev;
        Span* next = pos->_next;

        prev->_next = next;
        next->_prev = prev;
    }

private:
    Span* _head;
public:
    std::mutex _mtx; // 桶锁,访问控制
};

3.thread cache向中心缓存中申请,因为中心缓存你申请一个他会多给你几个,原则就是,至少给你一个

// 一次thread cache从中心缓存获取多少个
    static size_t NumMoveSize(size_t size)
    {
        assert(size > 0);

        // [2, 512],一次批量移动多少个对象的(慢启动)上限值
        // 小对象一次批量上限高
        // 小对象一次批量上限低
        int num = MAX_BYTES / size;
        if (num < 2)
            num = 2;

        if (num > 512)
            num = 512;

        return num;
    }
  1. Central Cache代码框架

// 单例模式
class CentralCache
{
public:
    static CentralCache* GetInstance()
    {
        return &_sInst;
    }

    // 获取一个非空的span
    Span* GetOneSpan(SpanList& list, size_t byte_size);

    // 从中心缓存获取一定数量的对象给thread cache
    size_t FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size);

private:
    SpanList _spanLists[NFREELIST];//和freelist采取相同的映射方式

private:
    CentralCache()
    {}

    CentralCache(const CentralCache&) = delete;

    static CentralCache _sInst;
};
CentralCache CentralCache::_sInst;

// 获取一个非空的span,从page中申请
Span* CentralCache::GetOneSpan(SpanList& list, size_t size)
{
    // ...
    return nullptr;
}

// 从中心缓存获取一定数量的对象给thread cache
size_t CentralCache::FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size)
{//start和end为输出型参数,里面记录了成功申请的内存起始结尾【begin,end】
    size_t index = SizeClass::Index(size);
    _spanLists[index]._mtx.lock();

    Span* span = GetOneSpan(_spanLists[index], size);
    assert(span);
    assert(span->_freeList);

    // 从span中获取batchNum个对象
    // 如果不够batchNum个,有多少拿多少
    start = span->_freeList;
    end = start;
    size_t i = 0;
    size_t actualNum = 1;
    while ( i < batchNum - 1 && NextObj(end) != nullptr)
    {
        end = NextObj(end);
        ++i;
        ++actualNum;//记录拿掉的内存对象个数
    }
    span->_freeList = NextObj(end);
    NextObj(end) = nullptr;

    _spanLists[index]._mtx.unlock();

    return actualNum;
}
  1. freelist新增函数

void PushRange(void* start, void* end)
    {
        NextObj(end) = _freeList;
        _freeList = start;
    }

5.现在就可以解决之前的问题,如果freelist要申请的内存位置为空,那么就要向中心缓存中申请

void* ThreadCache::Allocate(size_t size)
{
    assert(size <= MAX_BYTES);
    size_t alignSize = SizeClass::RoundUp(size);
    size_t index = SizeClass::Index(size);

    if (!_freeLists[index].Empty())
    {
        return _freeLists[index].Pop();
    }
    else
    {
        return FetchFromCentralCache(index, alignSize);
    }

void* ThreadCache::FetchFromCentralCache(size_t index, size_t size)
{
    // 慢开始反馈调节算法
    // 1、最开始不会一次向central cache一次批量要太多,因为要太多了可能用不完
    // 2、如果你不要这个size大小内存需求,那么batchNum就会不断增长,直到上限
    // 3、size越大,一次向central cache要的batchNum就越小
    // 4、size越小,一次向central cache要的batchNum就越大
    size_t batchNum = std::min(_freeLists[index].MaxSize(), SizeClass::NumMoveSize(size));
    if (_freeLists[index].MaxSize() == batchNum)
    {
        _freeLists[index].MaxSize() += 1;
    }
//maxsize()为freelist的成员变量,初始值为1

    void* start = nullptr;
    void* end = nullptr;
    size_t actualNum = CentralCache::GetInstance()->FetchRangeObj(start, end, batchNum, size);
    assert(actualNum > 1);

    if (actualNum == 1)
    {
        assert(start == end);
        return start;
    }
    else
    {
        _freeLists[index].PushRange(NextObj(start), end);
        return start;
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值