当前位置: 首页 > news >正文

并发内存池(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&#xff0c;即thread-cache malloc&#xff08;线程缓存的malloc&#xff09;&#xff0c;实现了高效多线程的内存管理&#xff0c;可实现对系统提供的内存分配函数malloc和free的替代…...

本地起一个VUE 前端项目

#安装 安装 Vue CLI 3&#xff1a; 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应用程序测试的开源工具。它允许开发人员模拟用户在浏览器中的交互行为&#xff0c;以便自动执行各种测试任务&#xff0c;包括功能测试、性能测试和回归测试等。Selenium最初是为Web应用程序测试而创建的&#xff0c;但它也可用于Web数据抓取和其他…...

每日刷题|回溯法解决全排列问题第二弹之解决字符串、字母大小排列问题

食用指南&#xff1a;本文为作者刷题中认为有必要记录的题目 前置知识&#xff1a;回溯法经典问题之全排列 ♈️今日夜电波&#xff1a;带我去找夜生活—告五人 0:49 ━━━━━━️&#x1f49f;──────── 4:59 …...

python循环遍历字典: title_content_list.append([key, value])print(ti

示例示例Python循环遍历字典的方法有以下几种&#xff1a;使用for...in循环&#xff1a; Python循环遍历字典的方法有以下几种&#xff1a; 1. 使用for...in循环&#xff1a; python dict {name:Tom, age:20, gender:male} # 遍历所有的键 for key in dict:print(key) # 遍…...

Redis List类型命令 - Set类型命令 - SortedSet类型命令

目录 List类型 什么是双向链表呢&#xff1f; List类型的特征&#xff1a; List的常用命令 LPUSH和RPUSH的区别&#xff1a; LPOP和RPOP的区别&#xff1a; LPUSH和RPUSH的使用 LPOP和RPOP的使用 LRANGE key star end&#xff1a;返回一段距离范围内所有的元素 BLPOP…...

等级保护 —— 安全控制点,安全要求

等级保护 —— 安全控制点&#xff0c;安全要求 安全物理环境&#xff1a; 物理位置选择 a&#xff09;机房场地应选择在具有防震、防风和防雨等能力的建筑内&#xff1b; 1&#xff09;核查是否有建筑物抗震设防审批文档。2&#xff09;核查是否有雨水渗漏的痕迹。3&#…...

nginx-缓存

disk cache&#xff1a;磁盘缓存数据&#xff0c;有时间延迟&#xff0c;但是非常小&#xff0c;相对于直接请求服务器返回 对于用户来说基本无感知。 memory cache&#xff1a;磁盘缓存数据&#xff0c;基本上没有时间延迟 协商缓存&#xff08;nginx自带功能&#xff0c; 不…...

layui使用富文本已经使用第三方插件Kz.layedit来优化layui的富文本

官方提供的编辑器功能太少 没有字体颜色&#xff0c;不能传图片&#xff0c;视频等扩展 官方文档说的很清楚&#xff0c;简易的富文本使用layui提供的的确十分方便&#xff0c;但是缺少的元素很多。像什么标题&#xff0c;元素&#xff0c;等简单的都没有。小编我当初页为此苦…...

某公司二面面试题总结

你们公司开发遵守怎么样的代码规范&#xff1f; 当编写Java代码时&#xff0c;遵守良好的代码规范对于代码的可读性和可维护性至关重要。以下是一些更详细的Java代码规范建议&#xff1a; 命名规范&#xff1a; 类名应该采用名词或名词短语&#xff0c;使用驼峰命名法&#xf…...

Ubuntu编译运行socket.io

本篇文章记录一下自己在ubuntu上编译运行socket.io的过程&#xff0c;客户端选用的是socket.io的c的库&#xff0c;编译起来倒不难&#xff0c;但是说到运行的话&#xff0c;对我来说确实是花了点功夫。毕竟程序要能运行起来才能更方便地去熟悉代码&#xff0c;因此今天我就记录…...

h5开发网站-页面内容不够高时,如何定位footer始终位于页面的最底部

一、问题描述&#xff1a; 在使用h5开发页面时&#xff0c;会遇到这个情况&#xff1a;当整个页面高度不足以占满显示屏一屏&#xff0c;页脚不是在页面最底部&#xff0c;影响用户视觉。想让页脚始终在页面最底部&#xff0c;我们可能会想到用&#xff1a; 1.min-height来控…...

手机也可以搭建个人博客?安卓Termux+Hexo搭建属于你自己的博客网站【cpolar实现公网访问】

文章目录 前言 1.安装 Hexo2.安装cpolar3.远程访问4.固定公网地址 前言 Hexo 是一个用 Nodejs 编写的快速、简洁且高效的博客框架。Hexo 使用 Markdown 解析文章&#xff0c;在几秒内&#xff0c;即可利用靓丽的主题生成静态网页。 下面介绍在Termux中安装个人hexo博客并结合…...

Support for password authentication was removed on August 13, 2021 解决方案

打开你的github&#xff0c;Setting 点击Developer settings。 点击generate new token 按照需要选择scope 生成token&#xff0c;以后复制下来。 给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优缺点 写作末尾 导读 当今数据计算领域主要的应用程序和模型可大致分为在线事务处理&#xff08;On-line Transaction Processing &#xff0c;OLTP&#…...

华为OD机试真题【寻找最大价值的矿堆】

1、题目描述 【寻找最大价值的矿堆】 给你一个由 ‘0’&#xff08;空地&#xff09;、’1’&#xff08;银矿&#xff09;、’2’&#xff08;金矿&#xff09;组成的的地图&#xff0c; 矿堆只能由上下左右相邻的金矿或银矿连接形成。超出地图范围可以认为是空地。 假设银矿…...

Java Maven 项目读取项目版本号

java读取 pom.xml 文件中设置的版本号 1. 在 src/main/resources/下新建 app.properties 文件&#xff1a; app.version${project.version} 2. 在pom.xml 中增加 <build> <resources> <resource> <directory>src/main/resources</di…...

Lesson4-1:OpenCV图像特征提取与描述---角点特征

学习目标 理解图像的特征知道图像的角点 1 图像的特征 大多数人都玩过拼图游戏。首先拿到完整图像的碎片&#xff0c;然后把这些碎片以正确的方式排列起来从而重建这幅图像。如果把拼图游戏的原理写成计算机程序&#xff0c;那计算机就也会玩拼图游戏了。 在拼图时&#xff…...

C++ 基础(一)题目练习

一、使用输出运算符输出一个长方形&#xff0c; 如下图所示&#xff1a; #include <iostream> using namespace std; int main() {cout << "*******" << endl;cout << "*******" << endl;cout << "*******"…...

Webpack5入门到原理

Webpack5学习 尚硅谷Webpack5新版视频教程 B站直达&#xff1a;https://www.bilibili.com/video/BV14T4y1z7sw 百度网盘&#xff1a;https://pan.baidu.com/s/114lJRGua2uHBdLq_iVLOOQ 提取码&#xff1a;yyds 阿里云盘&#xff1a;https://www.aliyundrive.com/s/UMkmCzdWsGh&…...

为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?

在建筑行业&#xff0c;项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升&#xff0c;传统的管理模式已经难以满足现代工程的需求。过去&#xff0c;许多企业依赖手工记录、口头沟通和分散的信息管理&#xff0c;导致效率低下、成本失控、风险频发。例如&#…...

linux arm系统烧录

1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 &#xff08;忘了有没有这步了 估计有&#xff09; 刷机程序 和 镜像 就不提供了。要刷的时…...

unix/linux,sudo,其发展历程详细时间线、由来、历史背景

sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作

一、上下文切换 即使单核CPU也可以进行多线程执行代码&#xff0c;CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短&#xff0c;所以CPU会不断地切换线程执行&#xff0c;从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)

目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关&#xff0…...

html-<abbr> 缩写或首字母缩略词

定义与作用 <abbr> 标签用于表示缩写或首字母缩略词&#xff0c;它可以帮助用户更好地理解缩写的含义&#xff0c;尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时&#xff0c;会显示一个提示框。 示例&#x…...

Springboot社区养老保险系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;社区养老保险系统小程序被用户普遍使用&#xff0c;为方…...

零知开源——STM32F103RBT6驱动 ICM20948 九轴传感器及 vofa + 上位机可视化教程

STM32F1 本教程使用零知标准板&#xff08;STM32F103RBT6&#xff09;通过I2C驱动ICM20948九轴传感器&#xff0c;实现姿态解算&#xff0c;并通过串口将数据实时发送至VOFA上位机进行3D可视化。代码基于开源库修改优化&#xff0c;适合嵌入式及物联网开发者。在基础驱动上新增…...

规则与人性的天平——由高考迟到事件引发的思考

当那位身着校服的考生在考场关闭1分钟后狂奔而至&#xff0c;他涨红的脸上写满绝望。铁门内秒针划过的弧度&#xff0c;成为改变人生的残酷抛物线。家长声嘶力竭的哀求与考务人员机械的"这是规定"&#xff0c;构成当代中国教育最尖锐的隐喻。 一、刚性规则的必要性 …...

对象回调初步研究

_OBJECT_TYPE结构分析 在介绍什么是对象回调前&#xff0c;首先要熟悉下结构 以我们上篇线程回调介绍过的导出的PsProcessType 结构为例&#xff0c;用_OBJECT_TYPE这个结构来解析它&#xff0c;0x80处就是今天要介绍的回调链表&#xff0c;但是先不着急&#xff0c;先把目光…...