【项目日记】高并发内存池 ---项目介绍及组件定长池的实现

在这里插入图片描述

余生还长,你别慌,也别回头,别念旧.
--- 余华 ---

1 高并发内存池简介

高并发内存池项目是实现一个高并发的内存池,他的原型是google的一个开源项目tcmalloctcmalloc全称Thread-Caching Malloc,即线程缓存的malloc,实现了高效的多线程内存管理,用于替代系统的内存分配相关的函数malloc free

这个项目是把tcmalloc最核心的框架简化后拿出来,模拟实现出一个自己的高并发内存池,目的是为了学习tcamlloc项目的精华,谷歌大厂的项目那必是含金量十足!这种方式有点类似我们之前学习STL容器的方式。但是相比STL容器部分,tcmalloc的代码量和复杂度上升了很多,大家做好心理准备。

难度的上升,我们的收获和成长也是在这个过程中同步上升。

·tcmalloc·是大厂google开源的,可以认为当时顶尖的C++高手写出来的,他的知名度也是非常高的,不少公司都在用它,Go语言直接用它做了自己内存分配器。所以这个项目可以称之为c++学习者的必学项目!

很多程序员是熟悉这个项目的,那么有好处,也有坏处。

  • 好处就是把这个项目理解扎实了,会很受面试官的认可。
  • 坏处就是面试官可能也比较熟悉项目,对项目会问得比较深,比较细。如果你对项目掌握得不扎实,那么就容易碰钉子。

有兴趣可以来看看源码哦:tcmalloc源代码在这里

涉及的技术栈有以下:

  1. 多线程编程:
    • 线程安全:确保在多线程环境下内存分配和释放操作的安全性。
    • 锁机制:使用各种锁(如自旋锁、互斥锁等)来同步对共享资源的访问。
    • 原子操作:利用原子操作来保证数据的一致性和线程安全。
  2. 内存管理:
    • 内存分配策略:设计高效的内存分配算法,减少内存碎片。
    • 内存池:预先分配一大块内存,按需分配给用户,减少系统调用开销。
    • 缓存机制:使用线程局部缓存来提高内存分配和释放的效率。
  3. 数据结构与算法:
    • 链表:管理空闲内存块。
    • 哈希表:快速查找和定位内存块。
    • 树结构:如二叉树、B树等,用于组织和管理内存块。
  4. 操作系统知识:
    • 虚拟内存管理:理解操作系统的内存管理机制。
    • 系统调用:如mmap、munmap等,用于直接操作内存。
  5. 编程语言:
    • 当然是C++:tmalloc通常是用C或C++编写的,因为这些语言提供了接近硬件的编程能力。

2 定长池的实现

我们先来实现一个高并发内存池的小组件 — 定长池 ,来练练手!
定长池也是基于池化技术:

所谓“池化技术”,就是程序先向系统申请过量的资源,然后自己管理,以备不时之需。之所以要申请过量的资源,是因为每次申请该资源都有较大的开销,不如提前申请好了,这样使用时就会变得非常快捷,大大提高程序运行效率

2.1 定长池的设计理念

直接使用C++的new delete或者是C语言的malloc free都会出现一种问题:内存碎片!
当我们在堆上开辟出许多空间,然后随机释放掉一些空间,虽然我们会得到很多空间,但是此时的空间就很可能会死不连续的了,不能继续开辟出更大的空间了:
在这里插入图片描述

而定长池内部有一块连续的大空间和一个自由链表,每次开辟是都会在自由链表中先进行选择可以使用的空间块,如果没有就在大空间中进行取出一部分进行使用!

2.2 框架搭建

定长池需要:

  1. 一段连续的大空间:我们使用char*来进行管理,方便一个一个字节来进行处理。
  2. 一个自由链表:进行资源的回收使用,内部通过链表结构链接起来
  3. 剩余资源数:进行资源空间的管理,不足够是进行扩容!
//--- 定长池的实现 ---#include<iostream>
#include<vector>
#include<memory>using std::cout;
using std::endl;template<class T>
class ObjectPool
{
public:T* New(){}void Delete(T*obj){}private://需要一个大空间char* _memory = nullptr;//回收资源的链表void* _freelist = nullptr;//剩余字节数!size_t _remainBytes = 0;
};

2.3 New 与 Delete

New函数的书写逻辑是:

  1. 如果是第一次进行new,那么就开辟一个大空间,然后取出需要的空间进行返回
  2. 如果不是第一次开辟,那么就要进行一个选择,如果自由链表中有内存块,就直接拿来使用。如果没有就在大空间中取出一部分进行使用!
  3. 每次开辟都要对剩余容量进行处理!

需要注意的是进行强制类型转换我们使用c++11通过的新接口来确保安全!

T* New()
{T* obj = nullptr;//先从自由链表中获取if (_freelist){obj = reinterpret_cast<T*>(_freelist);//进行头删//使用二级指针 取出指针的空间进行操作void* next = *reinterpret_cast<void**>(_freelist);_freelist = next;return obj;}//如果是第一次创建,那么_memory为空if (_remainBytes < sizeof(T)){//开辟大空间进行使用_remainBytes = 128 * 1024;//_memory = reinterpret_cast<char*>( malloc (_remainBytes ) )//直接使用系统调用进行优化_memory = reinterpret_cast<char*>( SystemAlloc (_remainBytes >> 13) );//开辟失败抛出异常if (_memory == nullptr){throw std::bad_alloc();}}obj = nullptr;//取出对应数量的空间obj = reinterpret_cast<T*>(_memory);//每次至少分配一个指针的大小,方便自由链表的使用size_t objSize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);//调整剩余参数_memory += objSize;_remainBytes -= objSize;//显示调用构造函数new(obj)T;return obj;
}

Delete函数的逻辑先将资源进行析构,然后不能将空间直接进行释放,而是将该空间直接头插到自由链表中!

特别需要注意的是头插的环节,如果是第一次删除,就直接等于就可以。反之就需要进行头插,需要先在空间中取出一个指针的空间来指向下一空间!!!

取指针空间的操作要使用二级指针来进行!使用一级指针(int*)是不合适的,因为一级指针解引用不能适配32位和64位(除非进行一些特殊判断,比较麻烦)。使用二级指针void**,解引用会直接取出void*的大小,真好就是对应指针的大小,就可以进行取出使用了!

void Delete(T*obj)
{//显示调用进行析构obj->~T();//将删除的空间插入到自由链表中//第一次进行插入if (_freelist == nullptr){_freelist = obj;*reinterpret_cast<void**>(_freelist) = nullptr;}//不是第一次就进行头插else{*reinterpret_cast<void**>(obj) = _freelist;_freelist = obj;}
}

2.4 系统调用优化

为了追求更高的效率,我们可以使用底层的系统调用来进行:
这样就需要对不同的操作系统进行区分处理了!只针对特定场景进行处理,不在进行maloc,直接按页传递内存!

#ifdef _WIN32#include<windows.h>
#else
// linux
#endifusing std::cout;
using std::endl;// 直接去堆上按页申请空间
inline static void* SystemAlloc(size_t kpage)
{
#ifdef _WIN32void* ptr = VirtualAlloc(0, kpage << 13, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else// linux下brk mmap等
#endifif (ptr == nullptr)throw std::bad_alloc();return ptr;
}

3 性能测试

我们完成了定长池的书写,接下来我们来看看他的效率怎么样!!!

struct TreeNode
{int _val;TreeNode* _left;TreeNode* _right;TreeNode():_val(0), _left(nullptr), _right(nullptr){}
};void TestObjectPool()
{// 申请释放的轮次const size_t Rounds = 5;// 每轮申请释放多少次const size_t N = 100000;std::vector<TreeNode*> v1;v1.reserve(N);size_t begin1 = clock();for (size_t j = 0; j < Rounds; ++j){for (int i = 0; i < N; ++i){v1.push_back(new TreeNode);}for (int i = 0; i < N; ++i){delete v1[i];}v1.clear();}size_t end1 = clock();std::vector<TreeNode*> v2;v2.reserve(N);ObjectPool<TreeNode> TNPool;size_t begin2 = clock();for (size_t j = 0; j < Rounds; ++j){for (int i = 0; i < N; ++i){v2.push_back(TNPool.New());}for (int i = 0; i < N; ++i){TNPool.Delete(v2[i]);}v2.clear();}size_t end2 = clock();cout << "new cost time:" << end1 - begin1 << endl;cout << "object pool cost time:" << end2 - begin2 << endl;
}

可以看到效率是快了10倍啊!!!很好很好!!!

在这里插入图片描述
这样定长池就完成了

定长池中最关键的有三点:

  1. 在32位 / 64位系统下取出一个指针的空间,使用二级指针,二级指针解引用一定是当前环境下只指针的大小!
  2. 自由链表的内存块的链接,需要特别注意,其中没有多加指针,而是是否巧妙的利用类型转换进行使用!
  3. 如何实现的内存分配以及资源复用?通过大空间的切割和自由链表的回收!!!

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RocketMQ Dashboard

rocketmq-dashboard是一个可视化查看和管理RocketMQ消息队列的工具 官方地址&#xff1a;RocketMQ Dashboard | RocketMQ 1、点击下载源码 2、下载并解压&#xff0c;切换至源码目录rocketmq-dashboard-1.0.0 3、修改配置文件 4、编译 rocketmq-dashboard打成jar包 &#xf…

MySQL中的回表查询、索引覆盖、索引下推

本文重点介绍索引中的常见概念&#xff1a;回表查询、索引覆盖、索引下推 一、回表查询 我们首先理解&#xff1a;在InnoDB存储引擎中&#xff0c;根据索引的存储形式&#xff0c;又可以分为以下两种&#xff1a; 分类含义特点聚集索引 (Clustered Index)将数据存储与索引放到…

leetcode 438.找到字符串中所有字母异位词

目录 题目描述 示例1&#xff1a; 示例2&#xff1a; 提示&#xff1a; 解题思路 Collections库 介绍 滑动窗口法 概念 应用场景及特点&#xff1a; 思路 流程展示 代码 复杂度分析 题目描述 给定两个字符串s和p&#xff0c;找到s中所有p的异位词的子串&#xf…

cdga|让数据治理真正内嵌于企业本身,释放企业数字化建设的最大价值

在当今这个数据驱动的时代&#xff0c;企业数据已成为最宝贵的资产之一&#xff0c;它不仅记录着企业的运营轨迹&#xff0c;更是指导决策、优化流程、创新产品与服务的关键力量。然而&#xff0c;要充分发挥数据的潜力&#xff0c;实现数字化转型的深度与广度&#xff0c;就必…

SAP 有趣的‘bug‘ 选择屏幕输入框没了

如下代码将会输出一个P_U的字段 PARAMETERS p_u TYPE string VISIBLE LENGTH 12 MEMORY ID m1.AT SELECTION-SCREEN OUTPUT.LOOP AT SCREEN.IF screen-name P_U.screen-invisible 1.MODIFY SCREEN.ENDIF.ENDLOOP. 如果我们给这个字段设置一个默认值&#xff0c;参考如下代码…

医疗器械法规标准相关资料

文章目录 前言如何查找法规文件与标准1. 法规清单2. 医疗器械法规文件汇编常用链接常见网站微信公众号前言 在前文 医疗器械软件相关法律法规与标准 中介绍了在软件设计过程常见的法规与标准,并给出部分标准如何查找和下载的方法,但是上文中列举的部分不全面,真实在产品设计…

集合及数据结构第十节(上)————优先级队列,堆的创建、插入、删除与用堆模拟实现优先级队列

系列文章目录 集合及数据结构第十节&#xff08;上&#xff09;————优先级队列&#xff0c;堆的创建、插入、删除与用堆模拟实现优先级队列 优先级队列&#xff0c;堆的创建、插入、删除与用堆模拟实现优先级队列 优先级队列的概念堆的概念堆的存储方式堆的创建变量的作…

审计发现 FBI 的数据存储管理存在重大漏洞

据The Hacker News消息&#xff0c;美国司法部监察长办公室 &#xff08;OIG&#xff09; 的一项审计发现&#xff0c; FBI 在库存管理和处置涉及机密数据的电子存储媒体方面存在“重大漏洞”。 OIG 的审计显示&#xff0c;FBI 对包含敏感但未分类 &#xff08;SBU&#xff09…

Nvidia驱动莫名其妙不好使了?nvidia-smi报错?如何解决?已解决!!

文章目录 一、报错提示二、解决方案2.1 原因1的解决办法2.2 原因2的解决方案 一、报错提示 Ubuntu20.04出现Failed to initialize NVML: Driver/library version mismatch问题NVIDIA-SMI has failed because it couldn‘t communicate with the NVIDIA driver. 二、解决方案 …

论文翻译:Multi-step Jailbreaking Privacy Attacks on ChatGPT

Multi-step Jailbreaking Privacy Attacks on ChatGPT https://arxiv.org/pdf/2304.05197 多步骤越狱隐私攻击对ChatGPT的影响 文章目录 多步骤越狱隐私攻击对ChatGPT的影响摘要1 引言2 相关工作3 对ChatGPT的数据提取攻击3.1 数据收集3.2 攻击制定3.3 从ChatGPT中提取私人数据…

网上商城|基于SprinBoot+vue的分布式架构网上商城系统(源码+数据库+文档)

分布式架构网上商城系统 目录 基于SprinBootvue的分布式架构网上商城系统 一、前言 二、系统设计 三、系统功能设计 5.1系统功能模块 5.2管理员功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍…

Mybatis-plus 创建自定义 FreeMarker 模板详细教程

FreeMarker 自定义模板官方步骤 网址&#xff1a;https://baomidou.com/reference/new-code-generator-configuration/#%E6%A8%A1%E6%9D%BF%E9%85%8D%E7%BD%AE-templateconfig &#xff08;页面往最下面拉为自定义模板相关内容&#xff09; 创建自定义FreeMarker 模板及使用…

Github文件夹重命名|编程tips·24-08-22

小罗碎碎念 这篇推文来解决一个问题&#xff0c;**我上传代码带Github以后&#xff0c;想要修改文件夹的名称怎么办&#xff1f;**例如&#xff0c;我要将文件夹24-08-22修改为联接散点图&#xff5c;24-08-22&#xff0c;可以遵循以下操作。 一、配置SSH 先登录github&#x…

一键生成原创文案的app有哪些?6款文案生成器值得分享

在这个信息爆炸的时代&#xff0c;文案创作的需求无处不在。为了提高文案创作的效率和质量&#xff0c;一键生成原创文案的app有哪些呢&#xff1f;对于这个问题&#xff0c;我们可以从市面上的文案生成器下手&#xff0c;因为文案生成器可以高效率的为创作者生产各种类型的文案…

韩语每日一句柯桥学韩语韩语零基础入门外贸韩语口语

韩语每日一词打卡&#xff1a;얹혀살다[언처살다]【动词】寄生,寄居。 原文:남의 집에 얹혀살지 말고 어렵더라도 직접 숙소를 구해야지. 意思&#xff1a;不要在别人家里寄居&#xff0c;哪怕困难也是要自己找一个住所。 【原文分解】 1、어렵다[어렵따]困难 2、직접[15857575…

wxpython Scintilla styledtextctrl滚动条拖到头文本内容还有很多的问题

wxpython Scintilla styledtextctrl滚动条拖到头文本内容还有很多的问题 使用wxpython Scintilla styledtextctrl&#xff0c;滚动条不自动更新 滚动条拖到头文本内容还有很多&#xff0c;如下&#xff1a; 以下是拖到最后的状态&#xff1a; 明显看出下图的滚动条的格子比…

手机谷歌浏览器怎么用

谷歌浏览器不仅在PC端受欢迎&#xff0c;在移动端也是广泛应用的。为了帮助大家更好的理解和使用手机谷歌浏览器&#xff0c;本文将详细介绍如何使用手机谷歌浏览器&#xff0c;对这款浏览器感到陌生的话就快快学起来吧。&#xff08;本文由https://chrome.cmrrs.com/站点的作者…

STM32——PWR电源控制的低功耗模式

1、理论知识 本节主要学习配置低功耗模式&#xff1a;防止在空闲时候耗电&#xff08;关闭/唤醒哪些硬件很重要&#xff09; 虽然STM32外部需要使用3.3V供电&#xff0c;但内部核心电路CPU、外设和存储器使用1.8V供电即可&#xff0c;这3者需要与外界交流时才需要3.3V供电 从上…

Qt之窗口

目录 Qt窗口简介: 菜单栏 ⼯具栏 状态栏 浮动窗⼝ 对话框 Qt内置对话框 1.消息对话框QMessageBox 2.颜⾊对话框QColorDialog 3.⽂件对话框QFileDialog 4.字体对话框QFontDialog 5.输⼊对话框QInputDialog 总结 接下来的日子会顺顺利利&#xff0c;万事胜…

网路安全-安全渗透简介和安全渗透环境准备

文章目录 前言1. 安全渗透简介1.1 什么是安全渗透&#xff1f;1.2 安全渗透所需的工具1.3 渗透测试流程 2. 使用 Kali Linux 进行安全渗透2.1 下载ISO镜像2.2 下载VMware Workstaion软件2.3 Kali Linux简介2.4 准备Kali Linux环境2.5 Kali Linux初始配置2.6 VIM鼠标右键无法粘贴…