【项目日记(3)】高并发内存池的整体框架设计

前言

现代很多的开发环境都是多核多线程,在申请内存的场景下,必然存在激烈的锁竞争问题。malloc本身其实已经很优秀,那么我们项目的原型tcmalloc就是在多线程高并发的场景下更胜一筹,所以这次我们实现的内存池需要考虑以下几方面的问题。

1.性能问题。
2.多线程环境下,锁竞争问题。
3.内存碎片问题。

一,内存池整体结构

我们采用了三层缓存结构来实现不同层次的功能,这三层缓存之间的联系是密不可分的。

1.ThreadCache – 线程缓存

线程缓存是每个线程独有的,只能用于小于256KB的内存的分配,线程从这里申请内存不需要加锁,每个线程独享一个cache,这也就是这个并发线程池高效的地方。

2.CentralCache – 中心缓存

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

3.PageCache – 页缓存

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

三层缓存的大致结构:

在这里插入图片描述

下面对这三层缓存结构进行更详细的介绍。

二,线程缓存结构介绍

thread cache是哈希桶结构,每个桶是一个按桶位置映射大小的内存块对象的自由链表,其实就是我们在定长池中实现的FreeList链表。每个线程都会有一个thread cache对象,这样每个线程在这里获取对象和释放对象时是无锁的。

在这里插入图片描述

1.申请内存

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

2.释放内存

当释放内存小于256k时将内存释放回thread cache,计算size映射自由链表桶位置i,将对象Push到_freeLists[i]中重新链接管理,当满足某种条件导致链表的长度过长,则回收一部分内存对象到central cache。

三,中心缓存结构介绍

central cache也是一个哈希桶结构,他的哈希桶的映射关系跟thread cache是一样的。不同的是他的每个哈希桶位置挂是SpanList带头双向循环链表结构,它的本质就是一个个大块内存span(页),不过每个映射桶下面的span中的大内存块被按映射关系切成了一个个小内存块对象挂在span的自由链表中,这里的自由链表也是我们在定长池中实现的FreeList链表。

所以,这一层的逻辑结构就是:一个链表的每个节点(span大块内存)下又挂了一个链表(span被切分成的一个个小块内存块)。

在这里插入图片描述

1.申请内存

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

2.释放内存

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

四,页缓存结构介绍

页缓存结构依旧是一个哈希桶结构,每个哈希桶的位置挂的也是一个SpanList结构,但是它和上两层结构有本质区别。上两层结构哈希桶的映射关系是一样的,并且是通过特定的哈希映射函数建立映射关系的,而本层则是按下标桶号映射的,也就是说第i号桶中挂的span都是i页内存。

在这里插入图片描述

向页缓存申请span的基本步骤:

当线程缓存的哈希桶中没有小块儿内存了,并且中心缓存对应的哈希桶中页没有span了,这时会来页缓存申请span回去做切分使用。假设申请的span是5页大小,那么就会去页缓存的五号桶中查看是否有span,若有span就直接返回若没有span,则需要去6到128号桶中寻找是否存在span,假设100号桶中有span,那么会将这个100页的大span切分为一个5页的小span和一个95页的大span,再将这个5页的span返回给中心缓存,这个95页的span重新挂到页缓存的95号桶中!假如6到128号桶都没有span,则会向系统申请一个128页的大块儿空间,再来切分成小span返回!

五,总结

本篇文章只是用文字对这三层结构的关系进行了大致的介绍,但是我们也可以发现这三层结构的巧妙。这三层的申请/释放内存都是环环相扣,只要满足某种条件就如同一根线一般把这三层结构串联在一起。

从下一篇文章开始,就要通过代码实现每一层的结构以及申请/释放内存的逻辑,细节多多,干货也多多!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值