内存池项目

内存池项目

一些共同的知识

std::array<std::atomic<void*>, FREE_LIST_SIZE> centralFreeList_;

这个就是创建了一个centralFreeList_,他是一个array数组,它里面的类型都是atomic<void*>的,表示原子指针,操作不可以被打断。长度是FREE_LIST_SIZE.

std::this_thread::yield();

这里的意思就是线程让步。用于自旋锁,如果检测到原子标志被占用,就线程让步,让这个线程处于就绪态,避免忙等待。等过一会这个线程再过来询问【线程不会消失,只是进入就绪态,让CPU可以去做别的事情】

void* next = *reinterpret_cast<void**>(result); 
            //一样的,result是void*类型,里面存的是下一个节点的地址,也是void*类型,
            //如果直接*(result)那么里面的就变成void型了
            //所以要先把result转成void**类型,再取其中的值,才是正确的下一个空闲内存节点的地址
  • 使用 std::memory_order_release 内存序,确保当前线程对 centralFreeList_[index] 的写操作不会被重新排序到其他写操作之后。
  • static_cast只能在相关的数据类型之间转换,例如int double,父类子类之间的类型转换,reinterpret_cast 是一种低级别的类型转换,用于在不同类型之间进行强制转换,即使这些类型之间没有直接的关联。

V2

threadcache

  • 使用thread_local确保每个线程创建自己独立的threadcache。 优势:各自的线程申请内存都通过各自的线程实例threadcache来获取内存,不会出现线程之间的争夺进而发生资源的浪费。
  • 整个设计都不包含锁(还是因为不存在多线程抢夺资源的情况)
  • 处理最频繁的请求,应对高性能
  • 接口:
  • void* allocate(size_t size); 申请填写申请大小
    void deallocate(void* ptr, size_t size); 释放填写释放位置和大小
  • 从中心缓存获取内存,填写index就好
    void* fetchFromCentralCache(size_t index);
    归还内存到中心缓存,填写ptr和大小,那个bytes我好像设置了但没用到,会根据size计算index
    void returnToCentralCache(void* start, size_t size, size_t bytes);
threadcache结构

每一个线程都会有一个这个线程缓存的实例

请添加图片描述

centralcache

用到的库:atomic类

给threadCache层分内存,是通过本层的空闲列表来分配的,thread层要多大的内存,传递一个索引(不同的索引代表不同的大小)

central就利用头插法返回对应的内存块地址。

central层是只有一个哈希桶,桶的每一个下标连接了很多内存块。

申请内存

当central层没有对应索引的内存的时候,向page层索取,通过需要申请的size字节数来计算需要的页数,然后申请到的内存按照申请的字节数/需要的字节数 = 获取到的对应index的内存块数量。将这些连接起来,头赋给哈希桶的头,然后就还是头删法解决了。

接收thread释放的内存

从thread收回来的,如果大于规定的一个内存块的大小,那么是从系统申请的,直接返还给系统;

反之就是thread从central申请的,头插法就好了。 在插入之前,如果同时进行的是不同index的插回,那么就直接根据不同的index插回就好了;如果多个进程插回一个index内存块,那么这个时候自旋锁和让步就发挥作用,每一次接收内存块回收的时候,都会使用test_and_set,来实现线程间的同步。

为什么不适用mutex而是原子操作的自旋锁?
特性test_and_set 自旋锁std::mutex 阻塞锁
实现方式基于原子操作,无需操作系统支持基于操作系统,可能涉及系统调用
性能锁持有时间短时性能更高锁持有时间长时性能更高
线程等待忙等待(消耗 CPU)阻塞(线程挂起,不消耗 CPU)
适用场景锁持有时间非常短,线程切换开销较高的场景锁持有时间较长,线程切换开销可以接受的场景
复杂度实现简单,轻量级实现复杂,功能更强大
centralcache的数据结构如下

跟threadcache是一样的。中间层。

pageCache

  • 负责直接与操作系统交互,通过mmap申请大块内存,以页为单位进行内存管理(4KB,4K字节)

  • 是内存池与操作系统之间的桥梁

  • central层传递给page层需要的页数,进行分配。以页进行分配的好处:

    1. 硬件层面的优化,现代计算机内存管理单元就是页为单位,可以与硬件的页面管理对齐;并且操作系统所分配的娶你内存空间也是以页为基本单位,获得更好的性能

    2. 所有的分配都对齐到页边界,便于后续回收之后的合并,这样可以形成多个地址连续的页,也就是一大块内存,可以减少外部内存碎片

  • span是一个自定义的页结构体,存储了1. 对应的页内存块的地址 2. 页数 3. 下一块连续内存页的地址

  • 页面合并的好处:减少外部内存碎片,同时便于管理,因为多个小span合并成了一个大span,并且当span足够大的时候,可以选择归还给操作系统

page层数据结构

请添加图片描述

为什么是找>=numpages的空闲链表,而不是==的呢?

在page层是允许这种情况,在没有==请求的numpages时,允许分配>numpages的span,切割出需要的numpage页后返回。

这么做的好处是,如果只允许分配完全匹配的span,反而会导致内存利用率降低;通过允许分配更大的内存可以减少向系统申请内存的频率

合并sapn是通过另一map类型的spanmap_实现的

这个存储了内存地址到span的映射void* ,同时还有span*。记录了已分配的和空闲的span的信息。
可以减少向系统申请内存的频率

合并sapn是通过另一map类型的spanmap_实现的

这个存储了内存地址到span的映射void* ,同时还有span*。记录了已分配的和空闲的span的信息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值