当前位置: 首页 > news >正文

内存池整体框架设计

目录

 一、项目简介

二、什么是内存池

(1)池化技术

(2)内存池

(3)内存池主要解决的问题

(4)malloc

三、TCmalloc的三层架构

(1)总体框架

(1)Thread Cache框架设计

(2)Central Cache框架设计

(3)Page Cache框架设计


 一、项目简介

        在c/c++中我们已经有了malloc和new来向堆中申请内存块,为什么还需要高并发内存池呢?

在多线程环境下,可能同一时刻有多个线程使用malloc来申请内存块,但是malloc是一个临界资源,这意味着我们需要对malloc加上互斥锁来保证临界资源访问的独立性,如果申请内存非常频繁,可能加锁带来的消耗比开辟内存的消耗更大,造成性能下降的问题。

        而在这种高并发环境下,使用内存池有显著的优势。内存池通过预先分配和回收固定大小的内存块,减少了内存碎片,提高了内存分配和释放的效率。更重要的是,在高并发场景下,内存池可以更好地管理对内存资源的访问,减少锁竞争,从而提高程序的性能。

        本项目的原型是google的一个开源项目tcmalloc,tcmalloc全称Thread-Caching Malloc,即线程缓存的malloc,实现了高效的多线程内存管理,用于替代系统的内存分配相关的函数(malloc、free)。

        但是由于google的代码仍然较为复杂,所以在各个大佬的简化下,有了我们现在模拟实现的精简版tcmalloc,在该项目中,我们可以学习到一些系统调用的联动,比如多线程相关,内存相关,以及实现一个项目的流程和方法。

二、什么是内存池

(1)池化技术

        所谓的池化技术,就是程序先向系统申请大量(过量)的资源,然后自己管理起来,以备不时之需,这样以后再想要该资源的时候,就不需要通过系统调用来向系统索要了,直接从我们管理的队列中取,减少了系统调用带来的巨大开销,从而大大提高了程序运行的效率。

        在计算机中,有很多地方都使用了池化技术,比如我们之前的文章中有实现过线程池,进程池,除了这些还有定长池(对象池)等。以服务器上的线程池为例,它的主要思想是:先启动若干数量的线程,让它们处于睡眠状态,当接收到客户端的请求时,唤醒池中某个睡眠的线程,让它来处理客户端的请求,当处理完这个请求,线程又进入睡眠状态。

(2)内存池

        内存池是指程序预先从操作系统申请一块足够大内存,此后,当程序中需要申请内存的时候,不是直接向操作系统申请,而是直接从内存池中获取;同理,当程序释放内存的时候,并不真正将内存返回给操作系统,而是返回内存池。当程序退出(或者特定时间)时,内存池才将之前申请的内存真正释放。

(3)内存池主要解决的问题

        内存池主要解决的当然是效率的问题,其次如果作为系统的内存分配器的角度,还需要解决一下内存碎片的问题。那么什么是内存碎片呢?

        再需要补充说明的是内存碎片分为外碎片和内碎片,上面我们讲的是外碎片问题。外部碎片是一些空闲的连续内存区域太小,这些内存空间不连续,以至于合计的内存足够,但是不能满足一些的内存分配申请需求。内部碎片是由于一些对齐的需求,导致分配出去的空间中一些内存无法被利用。内碎片就是你需要5个字节,但是我给你8个字节,这三个字节就是内碎片。内碎片问题,我们后面项目就会看到,那会再进行更准确的理解。

(4)malloc

        C/C++中我们要动态申请内存都是通过malloc去申请内存,但是我们要知道,实际我们不是直接去堆获取内存的,而malloc就是一个内存池。malloc() 相当于向操作系统“批发”了一块较大的内存空间,然后“零售”给程序用。当全部“售完”或程序有大量的内存需求时,再根据实际需求向操作系统“进货”。malloc的实现方式有很多种,一般不同编译器平台用的都是不同的。比如windows的vs系列用的微软自己写的一套,linux gcc用的glibc中的ptmalloc。

三、TCmalloc的三层架构

(1)总体框架

ThreadCache(线程缓存结构)
线程缓存是每一个线程独享的结构,线程申请和释放内存都在这个缓存中进行,只能用于小于256KB的内存分配.整个线程缓存结构是不用加锁的

CentralCache(中心缓存结构)
中心缓存是所有线程所共享的,thread cache是按需从central cache中获取的对象。central cache会在合适的时机回收thread cache中的对象,避免一个线程占用了太多的内存,而其他线程的内存吃紧,达到内存分配在多个线程中更均衡的按需调度的目的。central cache是存在竞争的,所以从这里取内存对象是需要加锁,首先这里用的是桶锁,其次只有thread cache的没有内存对象时才会找central cache,所以这里竞争不会很激烈

PageCache(页缓存结构)
页缓存是在central cache缓存上面的一层缓存,存储的内存是以页为单位存储及分配的,central cache没有内存对象时,从page cache分配出一定数量的page,并切割成定长大小的小块内存,分配给central cache。当一个span的几个跨度页的对象都回收以后,page cache会回收central cache满足条件的span对象,并且合并相邻的页,组成更大的页,缓解内存碎片的问题。

他的主要框架就像下图所示:

 

(1)Thread Cache框架设计

申请内存:
(1)当内存申请size<=256KB,先获取到线程本地存储的thread cache对象,计算size映射的哈希桶自由链表下标i。
(2)如果自由链表_freeLists[i]中有对象,则直接Pop一个内存对象返回。
(3)如果_freeLists[i]中没有对象时,则批量从central cache中获取一定数量的对象(一个或者多个span),插入到自由链表并返回一个对象。

释放内存:

(1) 当释放内存小于256k时将内存释放回thread cache,计算size映射自由链表桶位置i,将对象Push到_freeLists[i]。
(2)当链表的长度过长,则回收一部分内存对象到central cache。

        从上面的图片和描述可以分析出:

        ThreadCache这个类至少有_freelist来存储归还的小内存块,这个和我们之前实现的定长池并无差别,但是我们要实现的tcmalloc适用于所有大小的对象,并非存储同一个类型的对象。即不能只有单一的_freelist,而是需要一组_freelist[N]这样的自由链表数组,每一个自由链表存储一个大小的内存块,我们要求实现256kb以下都能使用该内存池,难道我们需要256k个自由链表吗?这显然太多了,我们采取了一些策略,让大小差不多的对象都使用一个自由链表,这个在实现的时候再来详细说明。

        而_freelist又至少有一个链表指针,和链表上的数量。

(2)Central Cache框架设计

申请内存:
(1)当thread cache中没有内存时,就会批量向central cache申请一些内存对象,这里的批量获取对象的数量使用了类似网络tcp协议拥塞控制的慢开始算法;central cache也有一个哈希映射的spanlist,spanlist中挂着span,从span中取出对象给thread cache,这个过程是需要加锁的,不过这里使用的是一个桶锁,尽可能提高效率。
(2) central cache映射的spanlist中所有span的都没有内存以后,则需要向page cache申请一个新的span对象,拿到span以后将span管理的内存按大小切好作为自由链表链接到一起。(3)central cache的中挂的span中use_count记录分配了多少个对象出去,分配一个对象给threadcache,就++use_count

释放内存:

(1) 当thread_cache过长或者线程销毁,则会将内存释放回central cache中的,释放回来时--use_count。当use_count减到0时则表示所有对象都回到了span,则将span释放回page cache,page cache中会对前后相邻的空闲页进行合并。

        CentralCache是一个所有线程都能看得到的临界资源,当ThreadCache中没有内存了,则从CentralCache中拿取,但是为了高效,CentralCache设计成桶锁,不同线程要从不同桶中拿数据的时候,就不会产生锁竞争,从而使得效率大大提高。

        CentralCache的结构也是一个哈希桶,与上面的ThreadCache的哈希规则一样,不过这里的桶中挂的不再是一个一个的小内存块了,而是一个个的Span,Span又管理着小内存块,所以Span这个结构体中需要记录小内存块的使用情况,剩余个数,当一个Span的小内存块都被归还到CentralCache时,可以考虑将该Span归还给PageCache,从而减少外碎片的情况。

(3)Page Cache框架设计

申请内存:
(1)当central cache向page cache申请内存时,page cache先检查对应位置有没有span,如果没有则向更大页寻找一个span,如果找到则分裂成两个。比如:申请的是4页page,4页page后面没有挂span,则向后面寻找更大的span,假设在10页page位置找到一个span,则将10页pagespan分裂为一个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页内存。

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

        简而言之,PageCacheCache中也是一个哈希桶,不过这个桶里面挂的是也是一个个的Span,从1页到128页的Span,当CentralCache需要Span的时候,就从对应的桶中拿一个Span给CentralCache,然后由CentralCache把这个Span切割成小块小块的内存块,交给ThreadCache使用。而PageCache是真正底层和系统调用打交道的人。

相关文章:

  • 网络安全应急响应-系统排查
  • Go语言-初学者日记(三):函数与方法
  • C 语 言 --- 指 针 2
  • MyBatis小技巧与MyBatis参数处理
  • 【Firewalld】Linux中firewall-cmd的基本使用
  • Runnable组件容灾回退机制 with_fallback 深度解析降低程序错误率
  • 单链表的实现 | 附学生信息管理系统的实现
  • 3D打印技术助力高精密零件制造与维修工具革新
  • C# Winform 入门(13)之通过WebServer查询天气预报
  • 网络钓鱼攻击的威胁和执法部门的作用(第一部分)
  • 架构师面试(二十六):系统拆分
  • 【Csharp】获取实时的鼠标光标位置,测试用——做窗口软件绘图需要确定光标位置
  • GenerationMixin概述
  • Python Cookbook-5.5 根据内嵌的数字将字符串排序
  • 清明假期间
  • 数据分析-Excel-学习笔记
  • AI大模型:(二)2.1 从零训练自己的大模型概述
  • 【LeetCode 热题100】55:跳跃游戏(详细解析)(Go语言版)
  • 用python来操作mysql(复习一,主要是mysql连接和授权)
  • 【清明折柳】写在扬马三周目后
  • 广告网站建设制作设计服务商/seo优化推荐
  • 网站建设价值/google play store
  • 网站内页设置多少个关键字最好/如何做好seo基础优化
  • 训做网站的心得体会范文/广告联盟怎么做
  • wordpress 友言/徐州关键词优化排名
  • 网站开发制作平台/做优化关键词