实现一个内存池

实现一个高并发的内存池

项目描述:

实现在多核多线程的环境下,效率较高的处理高并发的内存池。

项目目的:
  • 减少内存碎片
  • 提高效率,使得在特定的情况下平均运行效率该高于malloc
  • 解决在内存申请过程中的竞争问题
设计思路:
  • 以定长哈希映射的空闲内存池为基础
  • 使用三层缓存分配结构
三层的缓存结构

在内存池实现的时候,采用了三层的结构,通过三层的不同的结构来保证达到减少内存碎片,将低锁竞争的激烈程度。
在这里插入图片描述

ThreadCache

在这里插入图片描述

  • 第一层为线程缓存,线程直接从这里拿去内存。每个线程都有自己的thread cache, 所以不用加锁,这就保证了并发内存池的高效性。
  • 使用哈希映射一个存储不同大小数据块的内存块池,通过根据不同大小的对象,构建不同大小的内存的分配器,进行内存的高效分配。
  • 在内存分配的过程中会产生内存碎片,而内存碎片分为两种
    • 内碎片:是指因为在内存中会因为内存对齐的原因,在内存分配过程中,为了要对齐到响应的对齐位置,会在造成一定空间的浪费
    • 外碎片:是指在内存分配过程中会,当需要一块内存时,该内存,可能是从一个大块的内存上切割下来的,不断的切割,就会使大内存块变成小的内存块,等到需要大块内存时就会发现找不到。
  • 根据定长的结构对齐进行该进,将哈希映射的池分为4部分,他们的对齐数分别是8 、16 、128 、512,将这几部分的内碎片的产生进行一定的控制,从而达到减少内碎片的目的,而外碎片通过后面的合并进行解决。
CentralCache
  • 线程缓存从中心缓存获取对象,每个线程缓存按需从中心缓存获取数据。中心缓存只有一个,是所有线程所共享的

  • 中心缓存会周期性的从线程缓存中回收对象,避免因为一个线程占有太多的内存,从而导致其他的线程的内存紧张。因此中心缓存达到了均衡内存的目的,尽可能的保证内存的分配公平合理

  • 中心缓存存在线程安全问题,所以需要加锁进行保护,但是此处的取内存对象效率十分的高,所以不会存在很激烈的锁竞争的问题

在这里插入图片描述

PageCache

在这里插入图片描述

  • PageCache处于CentralCache的上一层缓存结构,当中心缓存中的内存数据块不够的话,就会从页缓存中进行申请,从而获得内存资源
  • 页缓存是以页为单位进行存储的,通过哈希的映射建立一个缓存池,每个下标对应该位置所挂的是几页的内存块,也就是span
在这里通过Span来描述包含连续多个对象数据块的内存,就是一个申请利连续内存数据块的集合。
  • 当中心缓存区中span满足一定的条件时,PageCache就会对其进行回收,然后进行合并,从而达到减少内存碎片的目的。
  • 当PageCache中的内存不够用时,直接向系统堆申请内存。
具体实现要点:
  • 关于CentralCache 与 PageCache 我们使用单例模式中,在运行过程中只创建一个对象

  • 对于线程缓存的申请采用慢增长的模式,尽量减少内存无谓的闲置浪费

  • 采用跨度span来表示一个连续内存块的集合,通过使用一个引用计数,完成对于闲置空间合并。

  • 申请释放内存,均是改变数据块的指向,操作效率较高,对一块空间可以重复的,使用unique_lock对哈希映射标下的每一个桶进行加锁,不影响其他的线程操作其他的链表,从而减轻锁的竞

  • 采用tls, 使得每个线程都有一个自己的全局变量,从而使每个线程都有自己的缓存池

TLS是各线程的独立的数据存储空间,使用TLS技术可在线程内部独立使用或者修改进程的全局数据或是静态数据,就像对待自身的局部变量一样。

项目中使用了windows 下的msvc的__declspec(thread)修饰符,
在linux下还可以使用gcc __thread修饰符进行声明。
这两者使用简单我们只需要在全局或静态变量前加上修饰符,然后在各自的线程中调用即可
  • 整个结构中均需要加锁,使用mutex 锁来保证线程的安全。 在整个环节中对于两部分进行加锁,一部分是二层的中心缓存, 对于每一给span 链表都要进行加锁。第三部分是对于页缓存进行加锁,对于缓存整个加锁对其进行保护,因为一般的操作都集中在第一层很少能够到达第二层或者第三层,所以因锁造成的效率一般很小。

  • 对于空闲块的合并, 在控制内存碎片的最重要的一步就是,进行合并。在内存块从系统中申请出来,以及从第二层分给每个线程的内存池时,都进行了页与span之间对应关系, 通过使用map进行存储。当有第二层有整块的空闲时,就将其送到第三层进行合并。在第三层通过查找前一块与后一块的内存,进行前后合并。并将合并出的内存挂到第三层。

程序框架
内存的申请
申请小于64K的空间
  • 此时申请的空间在三层缓存空间锁能分配的大小的范围之内,所以就从每个线程的各自的内存池中获取内存。
  • 首先根据所申请的空间的大小进行内存对齐找到其对其数,再根据其对其数确定该块空间在内存池中的下标,从中获取内存。
  • 如果在threadCache中没有内存,则从第二块内存中获取内存,从中取出相应的块数,返回所需的内存数,将剩余的内存挂到第一层的空闲链表池下。
  • 当第二层也没有相应的内存数就从第三层去获取,在内存获取时,通过遍历整个哈希映射表, 找到内存块,如果找到的内存块大于要申请的内存块就及进行分割,返回要申请的内存的大小,将剩余的内存继续挂到第三层。
  • 当第三层也没有足够的内存时,就直接从系统内存中进行申请。
申请大于64k 且小于 128页的空间
  • 因为此时申请的空间大于第二层所能分配的空间,但小于第三层所能分配的最大的空间。所以此时申请时绕过第一层从第三层直接申请
  • 释放时也直接将内存释放给第三层
申请大于128页的空间
  • 直接从系统是申请, 绕过三层的缓存结构
  • 释放时直接将该块空间进行释放
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值