并发内存池(C++)
项目简介
这个项目是实现了一个高效的并发内存池。它的原型的goggle的一个开源项目tcmalloc,即thread-cache malloc(线程缓存的malloc),实现了高效多线程的内存管理,可实现对系统提供的内存分配函数malloc和free的替代。
内存碎片
内存碎片分为外碎片和内碎片。
外碎片是指,未被分配给进程的内存块,由于其太小了,无法满足进程申请的内存大小。
内碎片是指,内存块已经分配给了进程,但是由于各种原因(比如结构体的内存对齐),不会使用内存块的某一部分,一直处于空闲状态,直至释放。
定长内存池设计
所谓定长,就是每次申请出来的内存大小都是一样的。
申请过程:首先在堆上申请一大块内存,按对象的大小从大块内存中截取对应的内存供进程使用。
释放过程:内存池中维护一个freelist_的链表,用来回收释放掉的内存,采用头插的方式挂在freelist_后边。在申请内存过程中,优先查看freelist_后边是否回收的内存块,优先使用freelist_中的内存供进程申请用。达到重复利用的目的。

项目框架
该内存池由3部分组成
1. thread cache:线程缓存是每个线程独有的,用于小于256KB的内存的分配,线程从这里申请内 存不需要加锁,每个线程独享一个cache,这也就是这个并发线程池高效的地方。
2. central cache:中心缓存是所有线程所共享,thread cache是按需从central cache中获取的对 象。central cache合适的时机回收thread cache中的对象,避免一个线程占用了太多的内存,而其他线程的内存吃紧,达到内存分配在多个线程中更均衡的按需调度的目的。central cache是存在竞争的,所以从这里取内存对象是需要加锁,首先这里用的是桶锁,其次只有thread cache的没有内存对象时才会找central cache,所以这里竞争不会很激烈。
3. page cache:页缓存是在central cache缓存上面的一层缓存,存储的内存是以页为单位存储及分配的,central cache没有内存对象时,从page cache分配出一定数量的page,并切割成定长大小的小块内存,分配给central cache。当一个span的几个跨度页的对象都回收以后,page cache 会回收central cache满足条件的span对象,并且合并相邻的页,组成更大的页,缓解内存碎片的问题。

内存池实现逻辑
threadcache
threadcache设计称为一个hash结构,申请的大小在其区间按各自的对齐数均匀划分桶。
其中hash的对其规则如下:

1B<= bites<128B,对齐数8B,其划分的桶就有128/8=16个,即8、16 ······ 128
8KB<=bites<64KB,对齐数1KB,其划分的桶就有(64 - 8)/1=56个,即9、10 ······ 64
64K<=bites<256KB,对齐数8K,其互粉的桶就有(256-64)/8=24个,即72、80 ······ 256
ps:这里的排列时不同的桶下应挂的小内存的大小,并不是hash桶的下标。其下标是按其排列顺序由低到高,由0开始的。
关于内碎片浪费的计算:
比如申请的大小为为129B,就会按16B对齐,实际申请128B+16B=144B,就会多出15B的内碎片,内存浪费率就是15B/144B=0.104。
再比如申请的大小为1025B,就会按128B对齐,实际申请1204+128=1332B,多出127B的内碎片,内存浪费率就是127B/1332B=0.095。
对上边内存规则有了一定认识之后,对下边threadcache的结构才会有更深刻的理解。

TLS(Thread Local Storage)线程本地存储技术:普通的全局变量在多线程中是贡献的,一个线程对其进行了修改,所有线程都能看见这个修改,而线程私有的全局变量与普通全局变量不同,线程私有的全局变量是线程的私有财产,每个线程都有自己的一份副本,某个线程对其所做的修改只会影响到自己的副本,并不会修改其他线程的副本。
申请内存
1、每个线程通过TLS无锁获取自己独属的ThreadCache对象
2、如果ThreadCache中对应hash桶下边的freelist_结构不为空,从中取出内存供进程使用
3、如果ThreadCache中对应hash桶下边的freelist_结构为空,向CentralCache中申请内存。
ps:向CentralCache中申请内存过程中,使用类似TCP拥塞控制中的满开始算法。以达到申请内存的多少的合理性(即并不是单个单个内存申请,而是批量申请)。
释放内存
1、将不用的内存,通过内存大小,计算出其在ThreadCache中对应桶的位置,将其插入进去。
2、当ThreadCache中某个桶中挂的链表过长的时候,即链表长度大于一次批量申请的内存时,就将ThreadCache中对应桶中的内存回收给CentralCache.
centralcache
centralcache设计也是一个hash结构,也是使用和threadcache的对齐规则。不同的是centralcache的每个hash桶挂着的是span对象,是一个双向链表。每个span中又切分了多个对应其对齐数的小内存对象,是一个单链表结构。

其次,从整个项目框架上看,多个ThreadCache内存不足或者需要释放内存的时候,都需要涉及到centralcache,因此centralcache最好设计称为单例模式,供多个threadcache访问。
申请内存
1、ThreadCache向CentralCache申请内存,首先获得从CentrlCache对应位置的hash桶中获得一个Span(此时需要加桶锁),在该Span中拿出ThreadCache申请的内存数(尽力而为)。
2、如果CentrlCache对应位置没有Span,则向PageCache中申请内存。将向PageCache中刚申请的内存进行切分成freelist_。
释放内存
1、回收ThreadCache中的freelist_链表过长的内存,首先得找到CentralCache中对应的hash位置
2、其次CentralCache得找到这个个回收回来得freelist_链表隶属于CentralCache对应hash位置下的哪一个span。
这里找到对应的span,使用了一个哈希结构,unordered_map<PAGE_ID, Span*>,在PageCache向CentralCache中分配内存的时候,就建立好了对应的映射关系了。
关于PAGE_ID的计算是这样的,(PAGE_ID)ptr >> PAGE_SHIFT,即将内存地址右移PAGE_SHIFT位。这样每一个内存地址都能找到自己的PAGE_ID。再通过映射关系找到自己所属的Span。
3、向对应hash位置下的对应span中依次挂入回收的链表,并且useCount对应减少。
4、当useCount减至0的时候,标志着该span中的所有小块内存都被回收回来了。就应该向PageCache中归还该span的内存了。
pagecache
pagecache的设计也是一个hash结构,采用的是直接定址法法映射。其每个hash桶下挂着的是以页为单位的对应其范围大小的span内存对象,是个双向链表结构。

这里的PageCache也设计称为单例模式。
申请内存
1、首先向满足CentralCache申请对应的桶中查看时候有span。有则交付一个span。
2、满足CentralCache申请对应的桶中没有span,就向后续桶中遍历查找存在内存的桶。
3、后续桶中的span必定比满足CentralCache申请对应的桶中的span大,需要在后续桶中的span切分满足CentralCache申请对应的桶中的大小,span剩下的内存则,按剩余内存大小映射插入到对应的hash桶中。
释放内存
1、回收来自CentralCache返还的span,进行合并页,缓解内碎片的问题。
2、向前合并,(1)前边没有页号了(2)前边的页号正在使用(3)合并后的页数超过128页,无法管理。则不合并。
3、向后合并,(1)前边没有页号了(2)前边的页号正在使用(3)合并后的页数超过128页,无法管理。则不合并。
内存池优化逻辑
大块内存的申请和释放
我们这里内存池的单个线程能申请的最大内存就是256KB,当线程申请的内存大于256KB的时候,便能是为是大块内存。
我们设计PageCache的时候,一页的大小定为的8K,当申请256KB的时候,就需要256KB/8KB=32个span。因此将Page Cache的哈希桶个数多弄几个,比如32*4=128个,即一个128页的span可以满足四个线程去申请256KB大小的内存。
当申请大块内存的时候,我们直接去向PageCache中去申请。
在PageCache中,当申请超过128页的大内存的时候,就直接向堆申请。
释放的过程相反即可。
释放大块内存的时候,直接向PageCache中归还。
在PageCache中,归还超过128页的内存时,直接归还给堆。
替代系统内存分配函数
我们的内存池是能替代系统提供的内存分配函数(malloc、free)的,因此我们不能在里边继续使用系统提供的内存分配函数。定长内存池便能达到使用内存分配的效果。
使用基数树优化
基数树,或称压缩前缀树,是一种更节省空间的Trie(前缀树)。对于基数树的每个节点,如果该节点是确定的子树的话,就和父节点合并。基数树可用来构建关联数组。
为什么要使用基数树优化呢?
在PageCache向CentralCache分配内存的时候,我们需要建立PageId和Span的映射关系,我们使用的是STL中unordered_map,其底层是使用的哈希表。关于哈希表有如下缺点不符我们内存池的使用:
1、哈希表的底层必定使用了系统内存分配相关函数
2、哈希表的底层使用数组实现的,数组大小不好确定,还涉及到扩容的问题(扩容就分为原地扩容和一定扩容)。
接下来就开始介绍基数树了!
单层基数树

单层基数树就如同hash的直接定址法一样,其中的模板参数表示的是存储页号的位数。在32位环境下是32-PAGE_SHIFT,64位环境下是64-PAGE_SHIFT。我们的PAGE_SHIFT是2^13,也就是说BITES在32位环境下的值就是2^19位。占用的内存就是2^19*4=2^21=2M。
比如页号为多少,就在对应的void**的数组中找对应的位置,其位置存储的就是它Span的信息。

双层基数树

上边的双层基数树的模板参数含义和单层基数树一样,都代表存储页号需要多少位。
这里的双层基数树的前5位用于标识页号,能通过第一层对应位置中的内容找到第二层的数据,第二层的数组标识着Span的属性。32位环境下,二层基数树需要的大小是2^19*4=2^21=2M。

项目反思
项目不足
可能存在内存泄漏。在ThreadCache的回收逻辑里,当哈希桶下挂的freelist_过长的时候,才将freelist_向CentralCache中返还。当freelist_挂的内存不长的时候呢?比如说就只挂了一个小的内存对象,并不满足过长的条件,就不会向CentralCache里返还,就造成了内存泄漏的问题。
项目优势
1、ThreadCache使用了TLS技术,每个线程无锁获得自己独属的ThreadCache对象。
2、ThreadCache内存不足时向CentralCache申请时,并不是需要多少申请多少,而是批量申请,也就是说在向CentralCache申请依次后,Thread Cache就有了一批内存对象供本线程使用。即减少了ThreadCache向CentralCache申请的频次,而CentralCache的访问时会加锁的,这也将使CentralCache中的桶锁不会很激烈。
3、使用基数树优化,既可以代替底层使用new和delete,有可以利用基数树可以根据一个长整型(比如一个长ID)快速查找到其对应的对象指针,比hash映射来的简单和节省空间。
相关文章:
并发内存池(C++)
项目简介 这个项目是实现了一个高效的并发内存池。它的原型的goggle的一个开源项目tcmalloc,即thread-cache malloc(线程缓存的malloc),实现了高效多线程的内存管理,可实现对系统提供的内存分配函数malloc和free的替代…...
本地起一个VUE 前端项目
#安装 安装 Vue CLI 3: Vue CLI是一个用于创建和管理Vue项目的命令行工具 npm install -g vue/cli#查看更详细的告警信息 npm install -g vue/cli --verbose#检查项目的依赖关系 ,保持项目的依赖关系最新和安全 npm audit npm audit fix#查看版本 vue --version#创建…...
Python爬虫:Selenium的介绍及简单示例
Selenium是一个用于自动化Web应用程序测试的开源工具。它允许开发人员模拟用户在浏览器中的交互行为,以便自动执行各种测试任务,包括功能测试、性能测试和回归测试等。Selenium最初是为Web应用程序测试而创建的,但它也可用于Web数据抓取和其他…...
每日刷题|回溯法解决全排列问题第二弹之解决字符串、字母大小排列问题
食用指南:本文为作者刷题中认为有必要记录的题目 前置知识:回溯法经典问题之全排列 ♈️今日夜电波:带我去找夜生活—告五人 0:49 ━━━━━━️💟──────── 4:59 …...
python循环遍历字典: title_content_list.append([key, value])print(ti
示例示例Python循环遍历字典的方法有以下几种:使用for...in循环: Python循环遍历字典的方法有以下几种: 1. 使用for...in循环: python dict {name:Tom, age:20, gender:male} # 遍历所有的键 for key in dict:print(key) # 遍…...
Redis List类型命令 - Set类型命令 - SortedSet类型命令
目录 List类型 什么是双向链表呢? List类型的特征: List的常用命令 LPUSH和RPUSH的区别: LPOP和RPOP的区别: LPUSH和RPUSH的使用 LPOP和RPOP的使用 LRANGE key star end:返回一段距离范围内所有的元素 BLPOP…...
等级保护 —— 安全控制点,安全要求
等级保护 —— 安全控制点,安全要求 安全物理环境: 物理位置选择 a)机房场地应选择在具有防震、防风和防雨等能力的建筑内; 1)核查是否有建筑物抗震设防审批文档。2)核查是否有雨水渗漏的痕迹。3&#…...
nginx-缓存
disk cache:磁盘缓存数据,有时间延迟,但是非常小,相对于直接请求服务器返回 对于用户来说基本无感知。 memory cache:磁盘缓存数据,基本上没有时间延迟 协商缓存(nginx自带功能, 不…...
layui使用富文本已经使用第三方插件Kz.layedit来优化layui的富文本
官方提供的编辑器功能太少 没有字体颜色,不能传图片,视频等扩展 官方文档说的很清楚,简易的富文本使用layui提供的的确十分方便,但是缺少的元素很多。像什么标题,元素,等简单的都没有。小编我当初页为此苦…...
某公司二面面试题总结
你们公司开发遵守怎么样的代码规范? 当编写Java代码时,遵守良好的代码规范对于代码的可读性和可维护性至关重要。以下是一些更详细的Java代码规范建议: 命名规范: 类名应该采用名词或名词短语,使用驼峰命名法…...
Ubuntu编译运行socket.io
本篇文章记录一下自己在ubuntu上编译运行socket.io的过程,客户端选用的是socket.io的c的库,编译起来倒不难,但是说到运行的话,对我来说确实是花了点功夫。毕竟程序要能运行起来才能更方便地去熟悉代码,因此今天我就记录…...
h5开发网站-页面内容不够高时,如何定位footer始终位于页面的最底部
一、问题描述: 在使用h5开发页面时,会遇到这个情况:当整个页面高度不足以占满显示屏一屏,页脚不是在页面最底部,影响用户视觉。想让页脚始终在页面最底部,我们可能会想到用: 1.min-height来控…...
手机也可以搭建个人博客?安卓Termux+Hexo搭建属于你自己的博客网站【cpolar实现公网访问】
文章目录 前言 1.安装 Hexo2.安装cpolar3.远程访问4.固定公网地址 前言 Hexo 是一个用 Nodejs 编写的快速、简洁且高效的博客框架。Hexo 使用 Markdown 解析文章,在几秒内,即可利用靓丽的主题生成静态网页。 下面介绍在Termux中安装个人hexo博客并结合…...
Support for password authentication was removed on August 13, 2021 解决方案
打开你的github,Setting 点击Developer settings。 点击generate new token 按照需要选择scope 生成token,以后复制下来。 给git设置token样式的remote url git remote set-url origin https://你的tokengithub.com/你的git用户名/仓库名称.git然后就可…...
MPP 与 SMP 的区别,终于有人讲明白了【文末送书】
文章目录 导读01 SMP1. SMP 的典型特征2. SMP的优缺点 02 分布式MPP计算架构1. MPP 架构核心原理2. MPP 典型特征3. MPP优缺点 写作末尾 导读 当今数据计算领域主要的应用程序和模型可大致分为在线事务处理(On-line Transaction Processing ,OLTP&#…...
华为OD机试真题【寻找最大价值的矿堆】
1、题目描述 【寻找最大价值的矿堆】 给你一个由 ‘0’(空地)、’1’(银矿)、’2’(金矿)组成的的地图, 矿堆只能由上下左右相邻的金矿或银矿连接形成。超出地图范围可以认为是空地。 假设银矿…...
Java Maven 项目读取项目版本号
java读取 pom.xml 文件中设置的版本号 1. 在 src/main/resources/下新建 app.properties 文件: app.version${project.version} 2. 在pom.xml 中增加 <build> <resources> <resource> <directory>src/main/resources</di…...
Lesson4-1:OpenCV图像特征提取与描述---角点特征
学习目标 理解图像的特征知道图像的角点 1 图像的特征 大多数人都玩过拼图游戏。首先拿到完整图像的碎片,然后把这些碎片以正确的方式排列起来从而重建这幅图像。如果把拼图游戏的原理写成计算机程序,那计算机就也会玩拼图游戏了。 在拼图时ÿ…...
C++ 基础(一)题目练习
一、使用输出运算符输出一个长方形, 如下图所示: #include <iostream> using namespace std; int main() {cout << "*******" << endl;cout << "*******" << endl;cout << "*******"…...
Webpack5入门到原理
Webpack5学习 尚硅谷Webpack5新版视频教程 B站直达:https://www.bilibili.com/video/BV14T4y1z7sw 百度网盘:https://pan.baidu.com/s/114lJRGua2uHBdLq_iVLOOQ 提取码:yyds 阿里云盘:https://www.aliyundrive.com/s/UMkmCzdWsGh&…...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...
基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...
DockerHub与私有镜像仓库在容器化中的应用与管理
哈喽,大家好,我是左手python! Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库,用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
自然语言处理——Transformer
自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效,它能挖掘数据中的时序信息以及语义信息,但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN,但是…...
GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...
短视频矩阵系统文案创作功能开发实践,定制化开发
在短视频行业迅猛发展的当下,企业和个人创作者为了扩大影响力、提升传播效果,纷纷采用短视频矩阵运营策略,同时管理多个平台、多个账号的内容发布。然而,频繁的文案创作需求让运营者疲于应对,如何高效产出高质量文案成…...
免费数学几何作图web平台
光锐软件免费数学工具,maths,数学制图,数学作图,几何作图,几何,AR开发,AR教育,增强现实,软件公司,XR,MR,VR,虚拟仿真,虚拟现实,混合现实,教育科技产品,职业模拟培训,高保真VR场景,结构互动课件,元宇宙http://xaglare.c…...
[ACTF2020 新生赛]Include 1(php://filter伪协议)
题目 做法 启动靶机,点进去 点进去 查看URL,有 ?fileflag.php说明存在文件包含,原理是php://filter 协议 当它与包含函数结合时,php://filter流会被当作php文件执行。 用php://filter加编码,能让PHP把文件内容…...
深入理解Optional:处理空指针异常
1. 使用Optional处理可能为空的集合 在Java开发中,集合判空是一个常见但容易出错的场景。传统方式虽然可行,但存在一些潜在问题: // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...
