Page cache 分配与合并内存资源的源头

thread cache找central cache要内存,但central cache没有span或者没有空闲的span就会去下一层找page cache

分析问题:

  1. 通过页号计算页的起始地址,页号<<PAGE_SHIFT,PAGE_SHIFT设置为8*1024

  1. span的起始地址=> void* start=(void*) (span->_pageId<<PAGE_SHIFT)

  1. 把span插到span桶,切分span,计算一个span的字节数size_t byte=span->_n<<PAGE_SHIFT ,将span大块内存切分成自由链表

  1. page cache需要一把大锁,可能多个thread可能同时取page和插入新page,切page也会涉及不同的桶,所以central cache要几个字节就去对应的桶,直接使用桶锁,而page cache需要大锁,并且page cahce也是唯一存在的,需要单例模式

  1. central cache没有空间的span,那就去找page cache 获取一个k页的span,page cache 没有就去找堆要,一找就找一个大的,因为大的才能连续,如果我想要2页的span没有,就去找大页切下来,大页切小,需要的自己用,不需要的接在page cache对应的桶链表后面,入锅page cache没有,就去找堆要128页的span,及2页给你用,126页接在page cache上

  1. 当thread cache太长满足一定条件以后,还给central cache 对应的span,当某个span 的use_count减为0,说明小内存全部回来,span还给page cache,还回来之后查一下页号,在page cache有空间的页就合并,page cache在不同的桶之间把大页切小,把小页合并

  1. page cache 第一个桶挂一页的span,第二个桶挂两页的span,按页数映射,central cache需要几页就去几页的桶

申请内存:

1. 当central cache向page cache申请内存时,page cache先检查对应位置有没有span,如果没有 则向更大页寻找一个span,如果找到则分裂成两个。比如:申请的是4页page,4页page后面没 有挂span,则向后面寻找更大的span,假设在10页page位置找到一个span,则将10页page span分裂为一个4页page span和一个6页page span。

2. 如果找到_spanList[128]都没有合适的span,则向系统使用mmap、brk或者是VirtualAlloc等方式 申请128页page span挂在自由链表中,再重复1中的过程。

3. 需要注意的是central cache和page cache 的核心结构都是spanlist的哈希桶,但是他们是有本质 区别的,central cache中哈希桶,是按跟thread cache一样的大小对齐关系映射的,他的spanlist 中挂的span中的内存都被按映射关系切好链接成小块内存的自由链表。而page cache 中的 spanlist则是按下标桶号映射的,也就是说第i号桶中挂的span都是i页内存。

释放内存:

如果central cache释放回一个span,则依次寻找span的前后page id的没有在使用的空闲span, 看是否可以合并,如果合并继续向前寻找。这样就可以将切小的内存合并收缩成大的span,减少 内存碎片。

确定需求之后就是代码的实现

  1. thread向central cache获取一个非空的span,如果没有就去找page cache申请

// 获取一个非空的span
Span* CentralCache::GetOneSpan(SpanList& list, size_t size)
{
    // 查看当前的spanlist中是否有还有未分配对象的span
    Span* it = list.Begin();
    while (it != list.End())
    {
        if (it->_freeList != nullptr)
        {
            return it;//找到了代表不用找page cache
        }
        else
        {
            it = it->_next;
        }
    }

//找不到空闲的span,需要向page cache申请
    // 先把central cache的桶锁解掉,这样如果其他线程释放内存对象回来,不会阻塞
    list._mtx.unlock();

//这里是获取page cache span,假设已经获取到了
    PageCache::GetInstance()->_pageMtx.lock();
    Span* span = PageCache::GetInstance()->NewSpan(SizeClass::NumMovePage(size));
    PageCache::GetInstance()->_pageMtx.unlock();

    // 对获取span进行切分,不需要加锁,因为这会其他线程访问不到这个span

    // 计算span的大块内存的起始地址和大块内存的大小(字节数)
    char* start = (char*)(span->_pageId << PAGE_SHIFT);
    size_t bytes = span->_n << PAGE_SHIFT;
    char* end = start + bytes;

    // 把大块内存切成自由链表链接起来
    // 1、先切一块下来去做头,方便尾插
    span->_freeList = start;
    start += size;
    void* tail = span->_freeList;
    int i = 1;
    while (start < end)
    {
        ++i;
        NextObj(tail) = start;
        tail = NextObj(tail); // tail = start;
        start += size;
    }

    // 切好span以后,需要把span挂到桶里面去的时候,再加锁
    list._mtx.lock();
    list.PushFront(span);

    return span;
}
  1. 计算一次向系统获取几页

// 一次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;
    }

static size_t NumMovePage(size_t size)
    {
        size_t num = NumMoveSize(size);
        size_t npage = num*size;

        npage >>= PAGE_SHIFT; //*8*1024
        if (npage == 0)
            npage = 1;

        return npage;
    }
  1. 获取一个k页的span

PageCachePageCache::_sInst;//单例模式
// 获取一个K页的span
Span* PageCache::NewSpan(size_t k)
{
    assert(k > 0 && k < NPAGES);

    // 先检查第k个桶里面有没有span
    if (!_spanLists[k].Empty())
    {
        return _spanLists->PopFront();
    }

    // 检查一下后面的桶里面有没有span,如果有可以把他它进行切分
    for (size_t i = k+1; i < NPAGES; ++i)
    {
        if (!_spanLists[i].Empty())
        {
            Span* nSpan = _spanLists[i].PopFront();
            Span* kSpan = new Span;

            // 在nSpan的头部切一个k页下来
            // k页span返回
            // nSpan再挂到对应映射的位置
            kSpan->_pageId = nSpan->_pageId;
            kSpan->_n = k;

            nSpan->_pageId += k;
            nSpan->_n -= k;

            _spanLists[nSpan->_n].PushFront(nSpan);

            return kSpan;
        }
    }

    // 走到这个位置就说明后面没有大页的span了
    // 这时就去找堆要一个128页的span
    Span* bigSpan = new Span;
    void* ptr = SystemAlloc(NPAGES - 1);
    bigSpan->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;
    bigSpan->_n = NPAGES - 1;

    _spanLists[bigSpan->_n].PushFront(bigSpan);

    return NewSpan(k);
}
//pagecache.h
class PageCache
{
public:
    static PageCache* GetInstance()
    {
        return &_sInst;
    }

    
    Span* NewSpan(size_t k);

    std::mutex _pageMtx;
private:
    SpanList _spanLists[NPAGES];

    PageCache()
    {}
    PageCache(const PageCache&) = delete;


    static PageCache _sInst;
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值