【高并发内存池】基本框架 + 固定长度内存池实现 1

高并发内存池

  • 1. 基本框架
  • 2. 定长内存池的实现
    • 2.1 介绍定长内存池
    • 2.2 T* New()
    • 2.3 void Delete(T* obj)
  • 3. 源码(附赠测试)
  • 4. 总结

1. 基本框架

高并发内存池主要由三个部分构成:
1.thread cache:用于小于256KB的内存的分配。线程缓存是每个线程独有的,线程从这⾥申请内存不需要加锁,每个线程独享⼀个cache,这也就是这个并发线程池⾼效的地⽅。

2.cetral 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对象,并且合并相邻的⻚,组成更⼤的⻚,缓解内存碎片的问题。
在这里插入图片描述

2. 定长内存池的实现

实现一个定长内存池,主要完成的功能就是两个:申请和释放。

2.1 介绍定长内存池

在这里插入图片描述
从图中可以看到_memory是我们向系统申请的一大块内存。
_freeList表示的是自由链表。

这里需要说明一下,为什么会使用自由链表:因为我们写的内存池,所以,申请得到的内存不是用完就立马还回去,而是用完之后用一个自由链表链接起来,这样下次再申请内存的时候就不用在堆里面申请了,而是直接在自由链表里面获取,大大的提高了效率。

定长内存池使用的流程
1.申请内存的时候,判断系统中是否有_freeList,如果有,判断_freeList的大小是否大于等于申请的内存,如果是,就直接从_freeList中拿。这些都不满足就去堆里申请了。

2.还回来内存的时候,直接将还回来的内存链入到_freeList的后面。

框架如下:

template<class T>
class ObjectPool
{
public:T* New(){}void Delete(T* obj){}
private:char* _memory = nullptr; // 指向大块内存的指针size_t _remainBytes = 0; // 大块内存在切分过程中剩余字节数void* _freeList = nullptr; // 还回来过程中链接的自由链表的头指针
};

2.2 T* New()

首先理解自由链表的结构:
在这里插入图片描述
每一个_freeList前面的部分存下一个自由链表的地址,这样就每个_freeList就链接起来了。

处理T* New()的逻辑:当系统要申请一块内存,我们设定为T* obj = nullptr;
此时需要判断是在堆上申请还是在从_freeList中拿。
如果_freeList不为空,就代表可以从_freeList中拿。

下面处理从_freeList中拿内存的逻辑:
可以理解为从_freeList中头删。

思考在单链表中是如何头删的?先保存下一个节点,然后让头节点指向下一个节点。

在这里插入图片描述

这里涉及一个问题:next如何取?
为什么说这是一个问题,因为我们不知道所要取用的大小,如果是char类型,就是1;如果是int类型,就是4;如果是double类型,就是8;这里要取用的是next,是一个地址,大小是指针大小。我们不知道指针大小是多少(这根据不同的平台会有不同)。

那么,我们该如何取一个指针的大小呢?
int*,存的是int的地址,int存的是int*的地址。(int)解引用就是int*的大小。

因此,我们可以使用 * (void**)表示的就是void*类型,而*((void**)_freeList)就表示取_freeList前指针大小个字节,也就是地址。

第二种情况,就是没有还回来的内存,没有_freeList,该怎么办呢?
在这里插入图片描述
这里没什么好说的。

obj取到了T大小的空间,还需要判断一下,这个T大小的空间是否比void*大,如果比void*小,要给void*大小的内存。这是因为我们至少要保证取下来的内存能存一个地址,不然无法链接回来了。
在这里插入图片描述

最终代码如下:

	T* New(){T* obj = nullptr;//1.优先把换回来的内存块再次重复利用if (_freeList)  //代表有还回来的内存块对象{//重复利用,进行单链表的头删 //这里的*(void**)_freeList表示取freeLiset的前指针大小个字节 -->也就是取next的地址void* next = *((void**)_freeList);     //?  这里为什么使用void**//obj是取整个对象obj = (T*)_freeList;//往后移动一位_freeList = next;}else  //如果没有还回来的内存块{//判断开辟的一大块空间是否够这次申请的if (_remainBytes < sizeof(T))  {//如果不够_remainBytes = 1024 * 128;   //申请128KB//这里为什么要这么写?_memory = (char*)malloc(_remainBytes);//SystemAlloc是系统调用接口//_memory = (char*)SystemAlloc(_remainBytes >> 13);//异常处理if (_memory == nullptr){throw std::bad_alloc();}}//创建一个对象指向memory,这个obj也就是我们申请内存给到的实体obj = (T*)_memory;//这里判断的意义是:如果我们申请的空间大于指针的大小,就返回我们申请的空间,//否则返回指针的大小  --> 这样做是为了内存块至少能存下地址size_t objSize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);//大块内存的指针向后移动_memory += objSize;//可用空间减少_remainBytes -= objSize;}new(obj) T;return obj;}

2.3 void Delete(T* obj)

这个过程可以看成是对_freeList的头插。
在这里插入图片描述

	void Delete(T* obj){//显式调用函数清理对象obj->~T();//头插//将obj内存块的前指针大小的字节存入_freeList的地址*(void**)obj = _freeList;//将_freeList移动为头结点_freeList = obj;}

3. 源码(附赠测试)

#pragma once
#include"Common.h"template<class T>
class ObjectPool
{
public://申请内存T* New(){T* obj = nullptr;//1.优先把换回来的内存块再次重复利用if (_freeList)  //代表有还回来的内存块对象{//重复利用,进行单链表的头删 //这里的*(void**)_freeList表示取freeLiset的前指针大小个字节 -->也就是取next的地址void* next = *((void**)_freeList);     //?  这里为什么使用void**//obj是取整个对象obj = (T*)_freeList;//往后移动一位_freeList = next;}else  //如果没有还回来的内存块{//判断开辟的一大块空间是否够这次申请的if (_remainBytes < sizeof(T))  {//如果不够_remainBytes = 1024 * 128;   //申请128KB//这里为什么要这么写?_memory = (char*)malloc(_remainBytes);//SystemAlloc是系统调用接口//_memory = (char*)SystemAlloc(_remainBytes >> 13);//异常处理if (_memory == nullptr){throw std::bad_alloc();}}//创建一个对象指向memory,这个obj也就是我们申请内存给到的实体obj = (T*)_memory;//这里判断的意义是:如果我们申请的空间大于指针的大小,就返回我们申请的空间,//否则返回指针的大小  --> 这样做是为了内存块至少能存下地址size_t objSize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);//大块内存的指针向后移动_memory += objSize;//可用空间减少_remainBytes -= objSize;}new(obj) T;return obj;}//这是释放的过程,将释放的小内存块挂到_freeList前面void Delete(T* obj){//显式调用函数清理对象obj->~T();//头插//将obj内存块的前指针大小的字节存入_freeList的地址*(void**)obj = _freeList;//将_freeList移动为头结点_freeList = obj;}
private:char* _memory = nullptr;         //指向大块内存的指针size_t _remainBytes = 0;         //大块内存切分过程中剩余字节数void* _freeList = nullptr;      //还回来过程中链接的滋有链表的头指针
};//测试
struct TreeNode
{int _val;TreeNode* _left;TreeNode* _right;TreeNode():_val(0), _left(nullptr), _right(nullptr){}
};void Test1()
{// 申请释放的轮次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;
}

4. 总结

本文主要讲了两个方面的内容,分别是高并发内存池的结构和实现定长内存池。

  1. 实现定长内存池的思路:
    1.1 从系统中申请T大小的内存obj,首先要判断系统中是否有_freeList,如果有,直接将_freeList头删,将切出来的节点给obj.
    1.2 如果没有_freeList,就直接从之前申请的大内存_memory中申请,如果_memory的大小不够申请的T的大小,就从堆上申请。如果_memory够大,就从_memory中切一个大小为T的内存块给obj.

2.如何取_freeList的前指针大小个节点。*(void**) obj = _freeList这句话就等价于obj->next = _freeList

*(void**) next = _freeList就表示取_freeList 的前指针大小个字节,而_freeList的前指针大小个字节存的是下一个内存块的地址,因此这句话表示取_freeList的下一个内存块,等价于next = _freeList->next.
在这里插入图片描述

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

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

相关文章

流域碳中和技术

随着全球气候变化的加剧&#xff0c;碳中和已成为实现可持续发展的重要目标之一。碳中和不仅仅是能源和工业领域的调整&#xff0c;它涉及整个生态系统的转型与再生。在这一过程中&#xff0c;流域的生态系统作为水、土、生物多样性等自然资源的集成体&#xff0c;扮演着至关重…

华为高级交换技术笔记 2024-2025

2024-2025 一、9/31.通信模型和封装2.以太网3.MAC地址4.以太网帧5.MAC地址表的建立 二、9/61.交换机的数据的处理2.以太网帧的分类3.广播域4.vlan技术开发背景 一、9/3 1.通信模型和封装 2.以太网 3.MAC地址 4.以太网帧 5.MAC地址表的建立 二、9/6 1.交换机的数据的处理 2.以…

【含2天数 / B】

题目 代码 #include <bits/stdc.h> using namespace std; int day[] {-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; bool has_2(int x) {while (x){if (x % 10 2)return true;x / 10;}return false; } bool check(int y, int m, int d) {return has_2(y) ||…

十二、JDK17的GC调优策略

文章目录 一、JVM有哪些参数可以调&#xff1f;二、从RocketMQ学习常用GC调优三部曲三、基于JDK17优化JVM内存布局1、定制堆内存大小2、定制非堆内存大小设置元空间设置线程栈空间设置热点代码缓存空间应用程序类数据共享 四、基于JDK17定制JVM的GC参数G1重要参数ZGC重要参数 五…

Android使用OpenCV 4.5.0实现扑克牌识别(源码分享)

一、显示效果展示 二、OpenCV 4.5.0 OpenCV 4.5.0是OpenCV&#xff08;Open Source Computer Vision Library&#xff0c;开源计算机视觉库&#xff09;的一个重要更新版本&#xff0c;该版本在多个方面进行了优化和新增了多项功能。 三、ONNX模型 ONNX&#xff08;Open Neu…

风云4A/4B卫星行列号和经纬度查找表文件下载及读取方式

下载地址 https://satellite.nsmc.org.cn/PortalSite/StaticContent/DocumentDownload.aspx?TypeID15 如图&#xff1a; 点击绿色小图标下载。 下载完了后是压缩包&#xff0c;解压缩后进入文件夹获取raw格式的文件 注意&#xff01;&#xff01; 注意133E对应的卫星文件…

植物检测系统源码分享

植物检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vision …

Java面向对象——内部类(成员内部类、静态内部类、局部内部类、匿名内部类,完整详解附有代码+案例)

文章目录 内部类17.1概述17.2成员内部类17.2.1 获取成员内部类对象17.2.2 成员内部类内存图 17.3静态内部类17.4局部内部类17.5匿名内部类17.5.1概述 内部类 17.1概述 写在一个类里面的类叫内部类,即 在一个类的里面再定义一个类。 如&#xff0c;A类的里面的定义B类&#x…

闪回科技再冲刺上市:曾夸大融资规模,毛利率下滑,有股东退出

近日&#xff0c;闪回科技有限公司&#xff08;下称“闪回科技”&#xff09;递交招股书&#xff0c;准备在港交所主板上市。据贝多财经了解&#xff0c;该公司曾于2024年2月递表&#xff0c;此次是“失效”后的更新版本&#xff0c;清科资本为其独家保荐人。 闪回科技在招股书…

力扣刷题之815.公交路线

题干描述 给你一个数组 routes &#xff0c;表示一系列公交线路&#xff0c;其中每个 routes[i] 表示一条公交线路&#xff0c;第 i 辆公交车将会在上面循环行驶。 例如&#xff0c;路线 routes[0] [1, 5, 7] 表示第 0 辆公交车会一直按序列 1 -> 5 -> 7 -> 1 ->…

全国职业院校技能大赛(大数据赛项)-平台搭建Spark、Scala笔记

Spark作为一个开源的分布式计算框架拥有高效的数据处理能力、丰富的生态系统、多语言支持以及广泛的行业应用。Scala是一种静态类型的编程语言&#xff0c;它结合了面向对象编程和函数式编程的特性&#xff0c;被誉为通用的“大数据语言”。而二者的结合更能迸发出新奇的化学反…

Dify创建自定义工具,调用ASP.NET Core WebAPI时的注意事项(出现错误:Reached maximum retries (3) for URL ...)

1、要配置Swagger using Microsoft.AspNetCore.Mvc; using Microsoft.OpenApi.Models;var builder WebApplication.CreateBuilder(args);builder.Services.AddCors(options > {options.AddPolicy("AllowSpecificOrigin",builder > builder.WithOrigins("…

Transformers | 在自己的电脑上开启预训练大模型使用之旅!

本文内容主要包括两部分&#xff1a; Hugging Face 社区介绍 如何使用 Transformers 库的模型 1. Hugging Face 社区介绍 Hugging Face (https://huggingface.co/) 是一个 Hub 社区&#xff0c;它和 GitHub 相同的是&#xff0c;他们都是基于 Git 进行版本控制的存储库社区&…

SRS流媒体服务器在宝塔面板下的安装

目录 一、安装 1、安装Docker 2、安装srs 二、测试 1、进入后台 2、推流 3、播放测试: (1)网页 (2)拉流 之前一篇文章,我们介绍了SRS流媒体服务器在CentOS下的安装,安装流程还是比较麻烦且耗时的,其实SRS支持Docker部署,今天我们介绍在宝塔面板的Docker中部署…

【C++篇】~类和对象(中)

类和对象&#xff08;中&#xff09; 1.类的默认成员函数​ 默认成员函数就是用户没有显式实现&#xff0c;编译器会自动生成的成员函数称为默认成员函数。一个类&#xff0c;我们不写的情况下编译器会默认生成以下6个默认成员函数&#xff0c;需要注意的是这6个中最重要的是前…

深入探索PostgreSQL优化器的代价模型(建议收藏)

PawSQL优化引擎的实现深受PostgreSQL优化器的影响&#xff0c;本文我们来揭密PostgreSQL的代价模型。PawSQL专注于数据库性能优化自动化和智能化&#xff0c;提供的解决方案覆盖SQL开发、测试、运维的整个流程&#xff0c;广泛支持MySQL、PostgreSQL、OpenGauss、Oracle等主流商…

数据脱敏-快速使用

1.数据脱敏定义 数据脱敏百度百科中是这样定义的&#xff1a; 数据脱敏&#xff0c;指对某些敏感信息通过脱敏规则进行数据的变形&#xff0c;实现敏感隐私数据的可靠保护。 因为在真正的生产环境中,很多数据是不能直接返回,但是我们工作的时候可能经常性的需要返回一些用户信…

历年大厂校招 网络安全面试题(80+经验贴)

《网安面试指南》http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247484339&idx1&sn356300f169de74e7a778b04bfbbbd0ab&chksmc0e47aeff793f3f9a5f7abcfa57695e8944e52bca2de2c7a3eb1aecb3c1e6b9cb6abe509d51f&scene21#wechat_redirect 《Java代码审…

为什么要使用多线程

为什么要使用多线程 任务分解&#xff1a;耗时的操作&#xff0c;任务分解&#xff0c;实时响应。数据分解&#xff1a;充分利用多核CPU处理数据。数据流分解&#xff1a;读写分离&#xff0c;解耦合设计。 #include <iostream> #include<thread> using namespac…

【Unity编辑器扩展】解决uGUI动效痛点 零代码可视化快速制作UI动效 DOTween Sequence可视化

UI动效痛点&#xff1a; UI动效一直是Unity游戏开发的一大痛点&#xff0c;大部分项目都在使用Animator/Animation制作UI动效。而Animation是以节点路径记录动画&#xff0c;一旦UI层级、节点名变更就会导致动效返工&#xff0c;且Animation编辑器缓动曲线很难控制&#xff0c…