【C++】从零实现一个高并发内存池

news2024/11/26 20:38:22

目录

项目简介

技术栈

内存池

内存池解决的主要问题

效率问题

内存碎片问题

整体框架设计

Thread Cache

代码框架

Central Cache

代码框架

Page Cache

代码框架

申请内存流程

Thread Cache

Central Cache

Page Cache

释放内存流程

Thread Cache

Central Cache

Page Cache


项目简介

本项目实现了一个具有三层缓存机制的高并发内存池,项目原型为 google 的开源项目 tcmalloc,tcmalloc 全称 Thread-Caching Malloc,即线程缓存的 malloc,实现了高效的多线程内存管理,用于替代系统的内存分配相关的函数(malloc、free)。

技术栈

C/C++,数据结构(链表、哈希桶),操作系统内存管理,单例模式,多线程,互斥锁。

内存池

内存池是指程序预先从操作系统申请一块足够大内存,此后,当程序中需要申请内存的时候,不是直接向操作系统申请,而是直接从内存池中获取;同理,当程序释放内存的时候,并不真正将内存返回给操作系统,而是返回内存池。当程序退出(或者特定时间)时,内存池才将之前申请的内存真正释放。

内存池解决的主要问题

内存池最关注的问题有两点:一是效率问题,二是内存碎片的问题。

效率问题

首先,我们知道申请内存使用的是 malloc,此函数设计得比较通用,什么场景下都可以用,这也就意味着什么场景下都不会有很高的性能。

malloc 是线程安全函数,但不是可重入函数。在多线程场景下申请内存时,malloc 通过递归锁实现了线程安全,保证多个线程互斥申请内存,这也就使得多线程不能并发申请内存。本项目为每个线程设计了一个 Thread Cache,使得多线程场景下可以并发申请内存。

关于 malloc / free 相关问题可以参考以下文章:

【C语言】一文详解 malloc / free 分配内存和释放内存相关问题-CSDN博客

内存碎片问题

外部碎片:当内存中有足够数量的区域来满足方法的内存请求,但是由于提供的内存是不连续的,因此无法满足进程的内存请求,会导致外部碎片,详见下图:


内部碎片:在分配给方法的内存比请求的内存稍大的情况下,分配的内存和请求的内存之间的差异
称为内部碎片。例如:页帧(page frame)是内存的最小可分配单元,也开始称作页框,Linux 下页帧的大小为4KB,若用户申请3KB,系统会根据最小可分配单元分配4KB,在用户层面仅分配了3KB,于是就只使用3KB,多出来的1KB就是内部碎片。

整体框架设计

现代很多的开发环境都是多核多线程,在申请内存的场景下,必然存在激烈的锁竞争问题。malloc 本身其实已经很优秀,那么我们项目的原型 tcmalloc 就是在多线程高并发的场景下更胜一筹,所以我们实现的内存池需要考虑以下几方面的问题。

  1. 性能问题。
  2. 内存碎片问题。
  3. 多线程环境下,锁竞争问题。

高并发内存池主要由如下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对象,并且合并相邻的页,组成更大的页,缓解内存碎片的问题

Thread Cache

Thread Cache 是哈希桶结构,每个桶是一个按桶位置映射对应内存块对象大小的 FreeList 自由链表。采用 TLS 技术使每个线程都有一个 Thread Cache 对象,这样每个线程在这里获取对象和释放对象时是无锁的。

代码框架

Central Cache

Central Cache 也是一个哈希桶结构,他的哈希桶的映射关系跟 Thread Cache 是一样的。不同的是他的每个哈希桶位置挂是 SpanList 链表结构,不过每个映射桶下面的 span 中的大内存块被按映射关系切成了一个个小内存块对象挂在 span 的自由链表中。

代码框架

Page Cache

Page Cache 依然用的桶结构,每个桶下挂的一个 SpanList,每个桶直接按照桶的下标映射 SpanList。

代码框架

申请内存流程

Thread Cache

  1. 当内存申请 size<=256KB,先获取到线程本地存储的 Thread Cache 对象,计算 size 映射的哈希桶自由链表下标 i,size 的映射关系如下表
  2. 如果自由链表 _freeLists[i] 中有对象,则直接 Pop 一个内存对象返回。
  3. 如果 _freeLists[i] 中没有对象时,则批量从 Central Cache 中获取一定数量的对象,插入到自由链表并返回一个对象。
申请的内存数对齐数哈希桶分区
[1, 128]8 bytefreelist[0, 16)
[128+1, 1024]16 bytefreelist[16, 72)
[1024+1, 8*1024]128 bytefreelist[72, 128)
[8*1024+1, 64*1024]1024 bytefreelist[128, 184)
[64*1024+1, 256*1024]8096 bytefreelist[184, 208)

Central Cache

  1. 当 Thread Cache 中没有内存时,就会批量向 Central Cache 申请一些内存对象,这里的批量获取对象的数量使用了类似网络 tcp 协议拥塞控制的慢开始算法;Central Cache 也有一个哈希映射的 SpanList,SpanList 中挂着 span,从span中取出对象给 Thread Cache,这个过程是需要加锁的,不过这里使用的是一个桶锁,尽可能提高效率。
  2. Central Cache 映射的 SpanList 中所有 span 的都没有内存以后,则需要向 Page Cache 申请一个新的 span 对象,拿到 span 以后将 span 管理的内存按大小切好作为自由链表链接到一起。然后从 span 中取对象给 Thread Cache。
  3. Central Cache 中挂的 span 中 use_count 记录分配了多少个对象出去,分配一个对象给Thread Cache,就 ++use_count。

Page Cache

  1. 当 Central Cache 向 Page Cache 申请内存时,Page Cache先检查对应位置有没有 span,如果没有则向更大页寻找一个 span,如果找到则分裂成两个。比如:申请的是4页 page,4页page后面没有挂span,则向后面寻找更大的 span,假设在10页 page 位置找到一个 span,则将10页 page span 分裂为一个4页 page span 和一个6页 page span。
  2. 如果找到 _spanList[128] 都没有合适的 span,则向系统使用 mmap、brk 或者是 VirtualAlloc 等方式申请128页 page span 挂在自由链表中,再重复步骤1中的过程。
  3. 需要注意的是 Central Cache 和 Page Cache 的核心结构都是 SpanList 的哈希桶,但是他们是有本质区别的,Central Cache 中哈希桶,是按跟 Thread Cache 一样的大小对齐关系映射的,Central Cache 的 SpanList 中挂的 span 中的内存都被按映射关系切好链接成小块内存的自由链表。而 Page Cache 中的 SpanList则是按下标桶号映射的,也就是说第 i 号桶中挂的 span 都是 i 页内存。

释放内存流程

Thread Cache

  1. 当释放内存小于256KB时将内存释放回 Thread Cache,计算 size 映射自由链表桶位置 i,将对象 Push 到 _freeLists[i]。
  2. 当链表的长度过长,则回收一部分内存对象到 Central Cache。

Central Cache

  1. 当 Thread Cache 过长或者线程销毁,则会将内存释放回 Central Cache中的,释放回来时--use_count。当 use_count 减到0时则表示所有对象都回到了 span,则将 span 释放回 page cache,Page Cache 会对前后相邻的空闲页进行合并。

Page Cache

  1. 如果 Central Cache 释放回一个 span,则依次寻找 span 的前后 page_id 的没有在使用的空闲 span,看是否可以合并,如果合并继续向前寻找。这样就可以将切小的内存合并收缩成大的 span,减少内存碎片。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2035377.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

P4213 【模板】杜教筛、P3768 简单的数学题、P3803 【模板】多项式乘法(FFT)

P4213 【模板】杜教筛 题目描述 P4213 【模板】杜教筛 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 运行代码 #include <iostream> #include <map>using namespace std;const int N 2000010; long long vis[N], pm[N], mu[N], phi[N], cnt; map<long lon…

解决手机按键失灵!全新检测方案了解一下!

手机按键在手机设备中起着至关重要的作用&#xff0c;手机按键用于执行各种操作&#xff0c;如接听电话、挂断电话、调节音量、开关机等&#xff0c;方便用户进行基本操作。在生产过程中视觉检测需要确保按键的尺寸、形状和表面光滑度符合设计要求&#xff0c;以保证按键的正常…

寒武纪提出视觉AI新高度:不再依赖LLM, Cambrian-1模型让世界看见多模态的力量!

论文标题: Cambrian-1: A Fully Open, Vision-Centric Exploration of Multimodal LLMs 作者团队&#xff1a;纽约大学谢赛宁, Yann LeCun等人 导读&#xff1a; 寒武纪1号(Cambrian-1)&#xff0c;一种以视觉为核心设计的多模态大语言模型&#xff08;MLLMs&#xff09;&…

技术速递|使用 Native Library Interop 为 .NET MAUI 创建绑定

作者&#xff1a;Rachel Kang 排版&#xff1a;Alan Wang 在当今的应用开发领域&#xff0c;通过利用本机功能来扩展 .NET 应用程序的能力非常宝贵。.NET MAUI 处理程序架构使开发人员能够使用 .NET 代码直接操作本机控件&#xff0c;甚至允许无缝创建跨平台自定义控件。然而&a…

【星闪开发连载】WS63E开发板Windows环境的构建

目录 HiSpark Studio安装 Python环境配置 SDK代码下载 新建工程 海思官方在gitee仓库中提供了一个文档介绍fbb_ws63: fbb_ws63代码仓为支持ws63和ws63e解决方案SDK。技术论坛&#xff1a;https://developer.hisilicon.com/forum/0133146886267870001 - Gitee.comhttps://gi…

WPF 数据模板DataTemplate、控件模板ControlTemplate、Style、ItemsPreseter

一言蔽之&#xff0c;Template就是“外衣”—— ControlTemplate是控件的外衣&#xff0c; DataTemplate是数据的外衣。 DataTemplate 它定义了一个数据对象的可视化结构 DataTemplate常用的地方有3处&#xff0c;分别是&#xff1a; ContentControl的ContentTemplate属性&…

提升体验:UI设计的可用性原则

在中国&#xff0c;每年都有数十万设计专业毕业生涌入市场&#xff0c;但只有少数能够进入顶尖企业。尽管如此&#xff0c;所有设计师都怀揣着成长和提升的愿望。在评价产品的用户体验时&#xff0c;我们可能会依赖直觉来决定设计方案&#xff0c;或者在寻找改善产品体验的切入…

八股总结----计算机网络

0.OSI七层模型 自己的理解&#xff1a;应用层&#xff1a;生成HTTP请求报文-----表示层&#xff1a;将请求报文转换成适合网络传输的数据格式&#xff0c;加密压缩编码等-----会话层&#xff1a;管理两个应用程序之间的会话&#xff0c;包括连接中断等------传输层&#xff1a…

HAProxy 效能飞跃先锋队

目录 一 负载均衡 1.1 四层负载 1.2 七层负载 1.3 四层负载和七层负载的区别 二 Haproxy简介 2.1 概念和内容 2.2 haproxy的基本配置信息 2.2.1 global 配置 2.2.2 proxies 配置 三 Haproxy的算法 3.1 静态算法 3.2 动态算法 3.3 其他算法 四 高级功能及配置 4.…

进程编程及其函数的使用

1. 创建进程 创建进程的核心操作是使用 fork() 系统调用。 1.1 fork() 系统调用 fork() 创建一个新进程&#xff08;子进程&#xff09;&#xff0c;新进程几乎是父进程的完整拷贝。fork() 返回两次&#xff1a; 在父进程中&#xff0c;返回子进程的 PID。在子进程中&#…

Typescript在AI产品中应用越来越广泛

AI产品中的应用 TypeScript 在 AI 产品中的应用逐渐增多&#xff0c;主要得益于其提供的类型安全、面向对象编程和模块化等特性&#xff0c;这些特性使得开发者能够构建可维护、可扩展和高性能的应用程序。 首先&#xff0c;TypeScript 作为 JavaScript 的超集&#xff0c;通…

C++类和对象(2)——取地址运算符重载

一、const成员函数 const放在成员函数参数列表后面进行修饰&#xff0c;那么这个成员函数就是const成员函数&#xff1b;const实际修饰的是成员函数形参中包含的this指针的形参&#xff0c;表明在这个成员函数内部不能对成员进行修改。 例如日期类里面的Print成员函数&#x…

【HarmonyOS NEXT星河版开发学习】综合测试案例-拼夕夕首页

个人主页→VON 收录专栏→鸿蒙开发小型案例总结​​​​​ 基础语法部分会发布于github 和 gitee上面&#xff08;暂未发布&#xff09; 前言 该实战案例并没有用到太多的知识点&#xff0c;只不过用到的一些新东西&#xff0c;要多花时间去熟悉手机app的一些页面&#xff0c;对…

【Python】Python单元测试基础

文章目录 01-单元测试基础什么是单元测试常用的文件结构运行单元测试 01-单元测试基础 什么是单元测试常用的文件结构编写第一个单元测试运行单元测试 什么是单元测试 单元测试是指一个自动化的测试&#xff1a; 用来验证一小段代码&#xff08;单元&#xff09;的正确性&#…

【LLM】医疗大语言模型:CareGPT

向AI转型的程序员都关注公众号 机器学习AI算法工程 CareGPT (关怀GPT)是一个医疗大语言模型&#xff0c;同时它集合了数十个公开可用的医疗微调数据集和开放可用的医疗大语言模型&#xff0c;包含LLM的训练、测评、部署等以促进医疗LLM快速发展。 特性&#xff1a; 添加ChatG…

【Datawhale AI 夏令营】动手学大模型应用开发Task1 Baseline 精读

【Datawhale AI 夏令营】动手学大模型应用开发Task1 Baseline 精读 开源大模型文件预览 Baseline 1.导入库 # 导入所需的库 from transformers import AutoTokenizer, AutoModelForCausalLM import torch import streamlit as st2.模型下载 # 源大模型下载 from modelscope…

将电脑打造成私人网盘,支持外网访问之详细操作教程

你想过把自己电脑打造成随时随地访问的网盘吗&#xff1f;就是那种拥有一个属于自己的影音库&#xff0c;不用担心被和谐&#xff0c;随时可以登录访问电脑上的各种文件&#xff0c;相比传统网盘省心又安全。 使用Everything和节点小宝将电脑搭建成私人网盘&#xff0c;可以实现…

嵌入式面经篇三——数据类型

文章目录 前言一、数据类型1、用变量 a 给出下面的定义2、下面的代码输出是什么&#xff0c;为什么&#xff1f;3、写出 float x 与“零值”比较的 if 语句。4、下面代码有什么错误&#xff1f;5、下面代码输出是什么&#xff1f;6、下面代码运行后会是什么现象&#xff1f;7、…

24年日语能力(JLPT)考试报名流程图解

报名方式 搜索JLPT中国教育考试网&#xff0c;在线报名&#xff0c;一般学生党从教育网入口登录&#xff0c;社会人士从公网入口登录 报名时间 N1-N5 8月20日 7:00 - 8月27日14:00 注册时间 8月13日7:00 - 8月27日14:00 报名步骤 阅读报考提示&#xff0c;注册个人信息→…

此处不允许使用 ‘空‘ 类型

说明&#xff1a;受最近看的书《设计模式之美》&#xff08;小争哥&#xff09;的影响&#xff0c;最近编码有意将一些业务逻辑写在对象里面&#xff0c;增强封装性。在此记录一次项目启动时的报错&#xff0c;如下&#xff1a; 原因&#xff1a;当你在实体类对象中&#xff0c…