高并发内存池扩展 -- 处理大内存,优化释放时需要传入空间大小,加入定长内存池,存放映射关系的容器的锁机制,优化性能(基数树,优势,优化前后对比)

目录

高并发内存池 扩展

测试

大内存

介绍

代码 

优化释放时需要传入空间大小

介绍

赋值

代码 

加入定长内存池

引入

介绍

代码

存放映射关系的容器 锁机制

写入

读取

优化性能

引入

基数树

单级基数树

两级基数树

三级基数树

优势 

引入代码

优化前后对比


项目介绍+定长内存池实现 -- 高并发内存池 -- 内存池介绍+问题,定长内存池原理+代码-优快云博客

整体框架+基本实现 -- 高并发内存池 -- 内存池介绍+问题,定长内存池原理+代码-优快云博客

高并发内存池 扩展

测试

void TestConcurrentAlloc1()
{
    Thread_Cache tc;
    void *p1 = tc.alloc(6);
    void *p2 = tc.alloc(8); // 剩1
    void *p3 = tc.alloc(1); // 拿1
    void *p4 = tc.alloc(7); // 剩2
    void *p5 = tc.alloc(8); // 拿1
    void *p6 = tc.alloc(8); // 拿1
    void *p7 = tc.alloc(8); // 剩3

    std::cout << p1 << std::endl;
    std::cout << p2 << std::endl;
    std::cout << p3 << std::endl;
    std::cout << p4 << std::endl;
    std::cout << p5 << std::endl;
    std::cout << p6 << std::endl;
    std::cout << p7 << std::endl;

    tc.dealloc(p1, 6);
    tc.dealloc(p2, 8);
    tc.dealloc(p3, 1);
    tc.dealloc(p4, 7);
    tc.dealloc(p5, 8);
    tc.dealloc(p6, 8);
    tc.dealloc(p7, 8);
}

可以看到,这里我们申请出来的内存块,地址都是连续的:

  • 符合我们之前维持了连续地址的特性 -- 在central cache中将切分出来的内存块尾插进链表 + thread cache 分配内存块是头删

大内存

介绍

thread cache最大支持256kb以下的内存申请

如果大于256kb呢?

  • 256kb等于64页(页大小为4kb的情况下),在要申请这么多页的情况下,从thread cache开始走流程显然不可能
  • 可以考虑从page cache开始
  • 从这里申请,也从这里归还 -- 归还后的空间可以被合并/切分后分配给其他线程

如果再大一点,大于128页呢?

  • 只能向堆申请了,page cache管不了128页以上的大内存

但依然需要借助page cache的alloc接口

  • 也就是要创建一个span来管理分配的内存,因为释放的时候需要传入空间大小
  • 如果只是一个指针,无法得知指向的空间是多大的

代码 

#include "helper.hpp"
#include "thread_cache.hpp"
#include <cassert>

void *concurrent_alloc(size_t size)
{
    if (size <= helper::THREAD_CACHE_MAX_SIZE)
    {
        // 通过TLS 每个线程无锁的获取自己的专属的ThreadCache对象
        if (TLS_ThreadCache == nullptr)
        {
            TLS_ThreadCache = new Thread_Cache;
        }
        return TLS_ThreadCache->alloc(size);
    }
    else // 直接走page cache流程
    {
        int page_size = 1 << helper::PAGE_SHIFT;
        // int page = (size % page_size > 0) ? (size / page_size + 1) * page_size : size >> helper::PAGE_SHIFT;
        int page = (size + page_size - 1) >> helper::PAGE_SHIFT;

        std::unique_lock<std::mutex> lock(Page_Cache::get_instance()->mtx_);
        Span *span = Page_Cache::get_instance()->alloc(page);

        return helper::pageid_to_ptr(span->start_pageid_);
    }
}

void concurrent_free(void *ptr,size_t size)
{
    if (size <= helper::THREAD_CACHE_MAX_SIZE)
    {
        assert(TLS_ThreadCache);
        TLS_ThreadCache->dealloc(ptr, size);
    }
    else
    {
        std::unique_lock<std::mutex> lock(Page_Cache::get_instance()->mtx_);
        Page_Cache::get_instance()->recycle(span);
    }
}
  Span *alloc(int page)
    {
        if (page > 128) // 直接向堆申请
        {
            void *ptr = helper::allocate_memory(page);
            // 创建并初始化span
            Span *span = new Span;
            span->n_ = page;
            span->start_pageid_ = helper::ptr_to_pageid(ptr);
            page_to_span_[span->start_pageid_] = span; // 用于在释放的时候拿到span结构,来确定分配的空间大小
            return span;
        }
        ...

优化释放时需要传入空间大小

介绍

因为我们要和free靠拢,free就只需要指针就可以正确释放空间

  • 解决这个问题其实也很简单

本身,我们能通过指针来找到空间对应的页号

  • 页号+页号->span的映射关系,我们可以找到空间对应的span结构

而每个span被切分成小块后,小块内存的大小都是一样的

  • 所以,我们可以直接在span结构内定义一个字段,记录申请时的内存块大小
  • 这样就不需要在释放接口处额外传入空间大小
  • 可以通过指针->页号->span->空间大小

赋值

  • 如果要走三层缓存,在切分对象时赋值
  • 如果走page cache/堆,在申请到span后赋值

这也就是为什么向堆申请空间后,依然要转成span结构,并且要建立页号和span的映射关系

  • 就是为了这里的优化
  • 因为这个大小本身就是被用来判断究竟走thread cache还是page cache的释放逻辑,无论存储的是实际使用大小,还是对齐后的大小,都不影响

代码 

void concurrent_free(void *ptr)
{
    Span *span = Page_Cache::get_instance()->obj_to_span(ptr);
    size_t size = span->size_;
    if (size <= helper::THREAD_CACHE_MAX_SIZE)
    {
        assert(TLS_ThreadCache);
        TLS_ThreadCache->dealloc(ptr, size);
    }
    else
    {
        std::unique_lock<std::mutex> lock(Page_Cache::get_instance()->mtx_);
        Page_Cache::get_instance()->recycle(span);
    }
}

central cache.hpp

​
Span *get_nonnull_span(std::unique_lock<std::mutex> &lock, int id, size_t size)
    {
        Span *span = span_map_[id].get_nonnull_span();
        if (span != nullptr)
        {
            return span;
        }
        else // 没有span就去page cache里要
        {
            span = alloc_page_cache(lock, id, size);
            span->size_ = size;
            // 切分成若干个对象
            ...

            return span;
        }
    }

​

page_cache.hpp

Span *alloc(int page)
    {
        if (page > 128) // 直接向堆申请
        {
            void *ptr = helper::allocate_memory(page);
            // 创建并初始化span
            Span *span = new Span;
            span->n_ = page;
            span->start_pageid_ = helper::ptr_to_pageid(ptr);
            span->size_ = page << helper::PAGE_SHIFT;
            page_to_span_[span->start_pageid_] = span; // 用于在释放的时候拿到span结构,来确定分配的空间大小
            return span;
        }
        Span *span = nullptr;
        // 当前有对应页的span
        if (!span_map_[page].empty())
        {
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值