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

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

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

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

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

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

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

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

7.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;
    }

  2. 计算一次向系统获取几页

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

  3. 获取一个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;
    };

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值