现代很多的开发环境都是多核多线程,在申请内存的场景下,必然会存在激烈的锁竞争问题,锁竞争会有一部分的性能损耗(因为需要阻塞等待)。malloc
本身其实已经很优秀,那么我们项目的原型tcmalloc
就是在多线程高并发的场景下更胜一筹,效率更加优秀。这次我们实现的内存池需要考虑以下几方面的问题:
针对这三个问题,将通过三个模块进行分层处理:
他们的关系是这样的:
在线程缓存底层,是一个大数组,每个数组是一个自由链表。按照一定规则下将256KB的内存进行对齐分配,共产生208个自由链表!
每次线程申请对于空间的地址,会找到映射的自由链表,并进行取出使用。如果为空了,就向中心缓存中申请!
申请内存:
释放内存:
中心缓存也是一个哈希桶结构,他的哈希桶的映射关系跟线程缓存是一样的。不同的是他的每个哈希桶位置挂是SpanList
链表结构,不过每个映射桶下面的span
中的大内存块被按映射关系切成了一个个小内存块对象挂在span
的自由链表中。
我们默认系统的一页内存是8KB,span
管理的大块儿内存实际上是大页内存,可能是一页,两页内存,这个大页内存会被切分为小块儿的内存,8字节对应的哈希桶中会切分为8字节的小块儿内存,这样可以很方便的将小块儿内存分配给线程缓存!
中心缓存就像是线程缓存的弹药包,中心缓存中的链表中每个对象,都是对应线程缓存链表的一个大弹药包,可以供线程内存使用!
申请内存:
spanlist
,spanlist
中挂着span
,从span
中取出对象给线程缓存,这个过程是需要加锁的,这里使用的是一个桶锁,尽可能提高效率。spanlist
中所有span
的都没有内存以后,则需要向页缓存申请一个新的span
对象,拿到span
以后将span
管理的内存按大小切好作为自由链表链接到一起。然后从span
中取对象给线程缓存。span
中有一个计数器use_count
记录分配了多少个对象出去,分配一个对象给线程缓存,就++use_count
!释放内存:
--use_count
。当use_count
减到0时则表示所有对象都回到了span
,则将span
释放回页缓存,页缓存中会对前后相邻的空闲页进行合并。page
,并切割成定长大小的小块内存,分配给中心缓存。当一个span
的几个跨度页的对象都回收以后,页缓存会回收中心缓存满足条件的span
对象,并且合并相邻的页,组成更大的页,缓解内存碎片的问题。申请内存:
span
,如果没有则向更大页寻找一个span
,如果找到则分裂成两个。
比如:申请的是4页page,4页page后面没有挂span,则向后面寻找更大的span,假设在10页page位置找到一个span,则将10页pagespan分裂为一个4页page span和一个6页page span。_spanList[128]
都没有合适的span
,则向系统使用mmap、brk
或者是VirtualAlloc
等方式申请128页page span
挂在自由链表中,再重复1中的过程。spanlist
的哈希桶,但是他们是有本质区别的: spanlist
中挂的span
中的内存都被按映射关系切好链接成小块内存的自由链表。spanlist
则是按下标桶号映射的,也就是说第i
号桶中挂的spa
n都是i页内存
。释放内存:
span
,则依次寻找span
的前后page id
,如果有对应ID的未使用的空闲span
,看是否可以合并,如果合并继续向前寻找。这样就可以将切小的内存合并收缩成大的span,减少内存碎片。