并发内存池(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&…...

地形有通挂支隘险远六种情况
地形有通、挂、支、隘、险、远六种情况 【安志强趣讲《孙子兵法》第34讲】 第十一篇:地形篇 【全文大白话】 地形有各种情况,行军有各种情况,用好地形获得交战的主动权。 【原文】 孙子曰:地形有通者,有挂者࿰…...

C++多态案例-设计计算器类
1.前置知识点 多态是面向对象的三大特性之一 多态分为两类 静态多态:函数重载和运算符重载都属于静态多态,复用函数名动态多态:派生类和虚函数实现运行时多态 静态多态和动态多态的区别 静态多态的函数地址早绑定-----编译阶段确定函数地…...

复制tr的一行数据或者复制数据使用,使用jq和php
效果图: 2.Html <!--复制的tr数据,s----------------------------------------------------------------------------------------------->{foreach from$arrs keykk itemvv} <tr><td style"text-align:center;" >1</t…...

软件测试的基础(1)
程序员(开发) :编写程序代码(实现产品需求) 产品:收集并设计需求-需求文档(根据用户需求进行产品设计) UI设计师:设计界面,向外展示的形态 前端:用代码实现页面的显示 DBA:数据库设计(系统数据之间的关联) 运维:版本控制和发布、升级迭代,环境搭建和维护 客服:客户支持,…...

基于Java+SpringBoot+Vue前后端分离库存管理系统设计和实现
博主介绍:✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专…...

Secrets in Kubernetes (K8s)
摘要 在Kubernetes(K8s)中,Secrets是一种用于存储敏感数据的资源对象。它可以用于存储密码、API密钥、数据库凭证等敏感信息,以便在应用程序中使用。 设计实现说明如下: 加密存储:Kubernetes使用Base64编…...

模板测试和深度测试在cocoscreator中的应用
模板测试(Stencil Test): 当片段着色器处理完一个片段之后,模板测试(Stencil Test)会开始执行,和深度测试一样,它也可能会丢弃片段。接下来,被保留的片段会进入深度测试,它可能会丢弃更多的片段。模板测试…...

手机便签功能在哪里?如何在便签里添加文字图片视频?
手机已成为我们生活中不可或缺的工具,而在使用手机的过程中,我们经常需要随手记录一些重要的事情。那么,如何高效便捷地记录这些事情呢?答案就是使用手机便签软件。但是,有很多人不知道手机便签功能在哪里?…...

Java 中 List 的 7 种遍历方式 及 性能对比
# for i 循环 for (int i 0; i < list.size(); i) {list.get(i); }# 增强for循环 for (int item : list) { }# iterator for 循环 for (Iterator<Integer> iterator list.iterator(); iterator.hasNext(); ) {iterator.next(); }# iterator while 循环 Iterator<…...

【Github】git本地仓库建立与远程连接
文章目录 前言一、git简介二、git下载2.1下载地址 三、git安装3.1安装3.2 配置3.3 config设置(增删改查) 四.github与git连接——本地Git仓库4.1 建本地的版本库4.2 源代码放入本地仓库4.3提交仓库 五、github与git的连接——远程连接5.1 创建SSH Key5.2…...