| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 数据结构与算法 -> 基于线程本地存储(TLS)的三层内存池 -> 正文阅读 |
|
[数据结构与算法]基于线程本地存储(TLS)的三层内存池 |
一、TLS简介? ??? ? ?在一个进程中,所有线程共享同一个地址空间。全局变量和静态变量能够被所有线程访问、修改。通过使用线程本地存储(Thread Local Storage,TLS)能够将数据和执行的特定线程联系起来。TLS是各线程独立的数据存储空间,声明的TLS变量在每个线程都有一个副本,在程序中可以安装相同方式来讨论,且互相不影响。 ? ? TLS分为静态的和动态的。静态TLS使用语言本身的关键字定义。C/C++中包括: 1、_thread 2、thread_local 3、_declspec(thread)? //g++/gcc编译器下用不了,主要在visual c++下使用 动态TLS通过调用系统API创建。 二、内存池简介项目参考tc_malloc,分以下三层。 ?? 1.Thread Cache? ? ? ? thread cache是内存池中的第一层缓存,这一层缓存能够解决并发状态下锁竞争的效率问题。线程在这里申请不需要加锁,每一个线程都有自己独立的cache,这也就是这个项目并发高效的地方。为了避免加锁带来的效率,在Thread Cache中使用TLS保存每个线程本地的Thread Cache的指针,这样Thread Cache在申请释放内存是不需要锁的。因为每一个线程都拥有了自己唯一的一个全局变量。 ? ? ?申请内存小于64k时在Thread Cache中申请内存,计算size在自由链表中的位置。如果自由链表中有内存对象,则直接从FreeList[i]中Pop一下对象,时间复杂度是O(1),且没有锁竞争。当FreeList[i]中没有对象时,则批量从Central Cache中获取一定数量的对象,插入到自由链表并返回一个对象。 ? ? ? ?当释放内存小于64k时将内存释放回Thread Cache,计算size在自由链表中的位置,将对象Push到FreeList[i]。当链表的长度过长,也就是超过一次最大限制数目时则回收一部分内存对象到Central Cache。 ?Thread Cache是一个对象数组,数组成员为一个个自由链表。 ?2.Central Cache? ? ?TLS虽然解决了内存碎片的问题,但也导致了内存分配不均衡的问题。当一个线程大量开辟内存再释放的时候,会存储大量无法被其他资源使用的空闲内存资源。为解决这个问题,设计出central cache,使程序能够周期性的回收thread cache中的内存资源,避免一个或多个线程占用大量内存资源,而造成其它线程内存资源不足,让内存资源的分配在多个线程中更均衡。 ? ? ? ?central cache是由spanlist组成的数组,其中spanlist是由span组成的双向链表。为了保证全局只有一个central cache,这个类设计成单例模式。Central Cache是存在竞争的,所以在这里取内存对象的时候需要加锁,但是锁的力度可以控制得很小。一个span对象大小是恒定的4K大小(32位下4K 64位下8K ) 但是中心缓存数组每个元素指定了单个span划分成内存块的大小 (比如第一个8bytes 第二个16bytes等等),故他们能挂载的内存块数不一样。 ? ? ? ?当Central Cache中没有非空的span时,则将空的span链在一起,向Page Cache申请一个span对象,span对象中是一些以页为单位的内存,切成需要的内存大小,并链接起来,挂到span中。Central Cache的span中有一个_usecount,分配一个对象给Thread Cache,就+_usecount。 ? ? ? ? 当Thread Cache过长或者线程销毁,则会将内存释放回Central Cache中的,释放回来时- -_usecount。当_usecount减到0时则表示所有对象都回到了span,则将Span释放回Page Cache,Page Cache中会对前后相邻的空闲页进行合并。由span对象划分出去的内存块和这个span对象是有归属关系的,所以由thread cache归还释放某个内存(比如16bytes)应该归还到central cache的16bytes部分的他所归属的那个span对象上。在Page Cache中维护一个页号到span的映射,当Page Cache给Central Cache分配一个span时,将这个映射更新到unordered_map中去,这样的话在central cache中的span对象下的内存块都是属于某个页的 也就有他的页号,,同一个span切出来的内存块PageID都和span的PageID相同,这样就能很好的找出某个内存块属于哪一个span了。也就能通过unorder_map映射到span的id上,从而找到span,在Thread Cache还给Central Cache时,可以查这个unordered_map找到对应的span。 ?3.Page Cache? ? ?? ? ? ???Page Cache可回收上层缓存中空闲的span并合并成更大的span,因此可解决内存碎片的问题。当central cache中无空闲span时也会将大的span切小分配。central cache和page cache中的span不同。?central cache中的span只代表一页 然后被分解成了小的内存块链接而成的链表。page cache中的span是有多页组成分的。 ????????当Central Cache向page cache申请内存时,Page Cache先检查对应位置有没有span,如果没有则向更大页寻找一个span,如果找到则分裂成两个。比如:申请的是4page,4page后面没有挂span,则向后面寻找更大的span,假设在10page位置找到一个span,则将10page span分裂为一个4page span和一个6page span。如果找到128 page都没有合适的span,则向系统使用map、brk或者是VirtualAlloc等方式申请128page span挂在自由链表中,再重复1中的过程。然后把这个4页的span对象切成4个一页的span对象 放在central cache中的16bytes位置。可以有四个结点,再把这四个一页的span对象切成需要的内存块大小。这里就是16bytes ,并链接起来,挂到span中。 ? ? ? ? 当Thread Cache过长或者线程销毁,则会将内存释放回Central Cache中。比如Thread cache中16bytes部分链表数目已经超出最大限制了,则会把后面再多出来的内存块放到central cache的16bytes部分的他所归属的那个span对象上。此时那个span对象的usecount就减一。当_usecount减到0时则表示所有对象都回到了span,则将Span释放回Page Cache,Page Cache中会依次寻找span的前后相邻pageid的span,看是否可以合并,如果合并继续向前寻找。这样就可以将切小的内存合并收缩成大的span,减少内存碎片。 ???????通过VirtualAlloc(Windows环境下是VirtualAlloc,Linux下使用brk或者mmap)直接向系统申请内存时,都是以页为单位的内存,在32位机器下,一页就是4K。所以从0开始每一页的起始地址都是4K的整数倍。将申请到的内存地址右移12位得到相应的页号,同样只需要将地址左移12位即得到每一页的起始地址。 ????????假如现在有一个PageID为50的3页的span,有一个PageID为53的6页的span。这两个span就可合并成一个PageID为50的9页的span,然后挂在9页的SpanList上。 |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/25 16:52:16- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |