002 高并发内存池_定长内存池设计

​🌈个人主页:Fan_558
🔥 系列专栏:高并发内存池
🌹关注我💪🏻带你学更多知识

来自必应

文章目录

  • 前言
    • 一、设计整体框架
    • 二、New操作(申请空间)
    • 三、Delete操作(用自由链表管理释放的空间)
    • 四、测试性能
  • 小结

前言

我们知道申请内存使用的是malloc,malloc几乎在什么场景下都可以用,意味着什么场景下都不会有很高的性能,下面我们就先来设计一个定长内存池做个开胃菜,学习它目的有两层,先熟悉一下简单内存池是如何控制的,第二它会作为我们后面内存池的一个基础组件。

一、设计整体框架

既然是内存池,那么我们首先得向系统申请一块内存空间,然后对其进行管理。要想直接向堆申请内存空间,在Windows下,可以调用VirtualAlloc函数;在Linux下,可以调用brk或mmap函数。_memory指针管理的就是我们向系统申请的一大块内存
在这里插入图片描述

1、 对于向堆申请到的大块内存,我们可以用一个指针来对其进行管理,但仅用一个指针肯 定是不够的,我们还需要用一个变量来记录这块内存的长度

size_t _SurplusBytes(剩余)

2、我们需要将这块内存进行切分,为了方便切分操作,指向这块内存的指针最好是字符指针,因为指针的类型决定了指针向前或向后走一步有多大距离,对于字符指针来说,当我们需要分配出去n个字节的空间时,直接对字符指针进行加n操作即可。

char* _memory

3、释放回来的定长内存块也需要被管理,我们可以将这些释放回来的定长内存块链接成一个链表,这里我们将管理释放回来的内存块的链表叫做自由链表,为了能找到这个自由链表,我们还需要一个指向自由链表的指针。

void* _freeList
这样我们所需的三个成员变量就定义好了
另外地,我们还需要引进一个概念:自由链表(这个结构我们在new与delete操作都会用到)

自由链表(free_List)是一种数据结构,通常用于管理可用内存块的列表。在动态内存分配中,当需要分配内存时,可以从自由链表中获取一个可用的内存块,当内存不再需要时,将其释放并加入到自由链表中。头部存储下一个节点的地址
在这里插入图片描述

整体代码框架如下:


#ifdef _WIN32
#include <Windows.h>
#else
#endifinline 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;
}template<class T>
class ObjectPool
{
public://申请空间T* New(){}//将空间释放还回到自由链表中void Delete(T* obj){}private:char* _memory = nullptr;	// 指向内存块的指针 size_t  _SurplusBytes = 0;		// 内存块中剩余字节数 void* _freeList = nullptr;  // 管理还回来的内存对象的自由链表的头指针
};

对代码进行补充说明

既然是内存池,那么我们首先得向系统申请一块内存空间,然后对其进行管理。要想直接向堆申请内存空间,在Windows下,可以调用VirtualAlloc函数;在Linux下,可以调用brk或mmap函数。
当前环境是Windows(_WIN32定义为真),我们则使用WindowsAPI中的VirtualAlloc函数来分配内存。VirtualAlloc函数可以用来将一段虚拟地址空间分配给进程,并将其映射到物理内存上。在具体的使用上我们直接用封装过的SystemAlloc分配内存即可。

二、New操作(申请空间)

当我们申请对象的时候,内存池应该优先将释放回来的空间重复利用(自由链表中的一个个内存块),这个操作是一个头删的操作

if (_freeList)
{obj = (T*)_freeList;//指向下一个_freeList = *(void**)_freeList;
}

在这里插入图片描述
如果自由链表的头指针_free_List为空,则应该由堆上申请的大块内存块中分配,具体操作:让_memory加等上OBJSIZE,让surplusBytes减等上OBJSIZE即可
在这里插入图片描述
这样我们的New操作就搞定了

T* New()
{T* obj = nullptr; //定义指向所申请空间的指针//从自由链表中申请if (_freeList){obj = (T*)_freeList;//指向下一个_freeList = *(void**)_freeList;}else{//扩容:如果剩余的内存块大小少于所申请的空间大小if (_SurplusBytes < sizeof(T)){_SurplusBytes = 128 * 1024;_memory = (char*)SystemAlloc(_SurplusBytes>>13);if (_memory == nullptr){throw std::bad_alloc();}}obj = (T*)_memory;size_t OBJSIZE = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);//分配空间_memory += OBJSIZE;		_SurplusBytes -= OBJSIZE;}//对开辟好空间的obj进行初始化//(使用定位new操作符对已经分配好的内存空间进行对象的构造(初始化)操作。当我们使用new关键字创建一个对象时,会在内存中分配一块空间来存储对象的成员变量,并调用对象的构造函数来对这块内存进行初始化。但是,在某些情况下,我们可能已经手动分配了内存空间,这时候就需要使用定位new操作符来调用对象的构造函数,以便正确地初始化这块内存空间。)new(obj)T;		return obj;
}

重点重点重点!!!

_freeList = *(void**)_freeList;
*(void**)是什么意思呢,为什么要这么写?

那就要从定长池的设计讲起了。在实现定长内存池时要做到“定长”有很多种方法,比如我们可以使用非类型模板参数,使得在该内存池中申请到的对象的大小都是N。定长内存池也叫做对象池,在创建对象池时,对象池可以根据传入的对象类型的大小来实现“定长”,因此我们可以通过使用模板参数来实现“定长”,比如创建定长内存池时传入的对象类型是int(32位下),那么该内存池就只支持4字节大小内存的申请和释放。

在这里插入图片描述

这就有一个问题了,假如在64位机器下,一个指针的大小是8字节,但是用户申请了1字节的空间,这时我们需要将空间扩为八字节再返回给用户,因为一旦这份空间被返回,1字节是无法当成指针使用存储下一个节点地址的!!!

当自由链表为空时,此时我们会从大块内存中分配空间,

当空间回收时,我们会用自由链表管理这份空间
_freeList = *(void**)_freeList;
解析:在32位下 *(void**)看的是_freeList头部四个字节,在64位下
看的是_freeList头部八个字节,这样我们就可以在每个节点头部存储下一个节点的地址,然后就可以把节点链接起来
以下这个代码是自由链表为空时,向大块内存中申请
疑问:为什么我们在大块内存中申请也是需要划定好固定大小呢?size_t OBJSIZE = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);

因为New操作的本质就是申请一块固定大小的空间,但是当这一块固定大小的空间被释放后,我们是需要重新拿来继续使用的(也是为了让自由链表的节点能够链接起来)
毕竟我们是不能确定机器是32位还是64位下的,当申请的空间小于32/64位下的指针,那么就开这个指针的大小,如果大于32/64位下指针的大小,那么就申请开辟空间的大小赋值给OBJSIZE

补充:当然你这里可以不用void,选择int一样可以

三、Delete操作(用自由链表管理释放的空间)

我们使用头插操作对已返还的空间做管理(当然尾插也行,只是还要找尾(ㄒoㄒ)~)

void Delete(T* obj)
{obj->~T();// 头插到freeList ,指向下一个*((void**)obj) = _freeList;_freeList = obj;
}

在这里插入图片描述

四、测试性能

比较在申请和释放大量TreeNode对象时,直接使用new和delete操作与使用对象池(ObjectPool)的效率差异

struct TreeNode
{int _val;TreeNode* _left;TreeNode* _right;TreeNode():_val(0), _left(nullptr), _right(nullptr){}
};void TestObjectPool()
{// 申请释放的轮次const size_t Rounds = 3;// 每轮申请释放多少次const size_t N = 100000;size_t begin1 = clock();std::vector<TreeNode*> v1;v1.reserve(N);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();//使用定长内存池ObjectPool<TreeNode> TNPool;size_t begin2 = clock();std::vector<TreeNode*> v2;v2.reserve(N);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;
}

在这里插入图片描述

小结

今日的项目分享就到这里啦,如果本文存在疏漏或错误的地方还请您能够指出!

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

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

相关文章

Redis 教程系列之Redis 数据类型(四)

Redis 数据类型 Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。 String(字符串) string 是 redis 最基本的类型,你可以理解成与 Memcached 一模一样的类型,一个 key 对应一个 value。 string 类型是…

B3626 跳跃机器人

题目描述 地上有一排格子&#xff0c;共 n 个位置。机器猫站在第一个格子上&#xff0c;需要取第 n 个格子里的东西。 机器猫当然不愿意自己跑过去&#xff0c;所以机器猫从口袋里掏出了一个机器人&#xff01;这个机器人的行动遵循下面的规则&#xff1a; 初始时&#xff0…

奇舞周刊第523期:来自 rust 生态的强烈冲击?谈谈 Leptos 在语法设计上的精妙之处...

奇舞推荐 ■ ■ ■ 来自 rust 生态的强烈冲击&#xff1f;谈谈 Leptos 在语法设计上的精妙之处 过去很长一段时间&#xff0c;前端框架们都在往响应式的方向发展。同时又由于 React hooks 的深远影响&#xff0c;函数式 响应式成为了不少前端心中最理想的前端框架模样。Solid …

设计模式-初步认识

目录 &#x1f6fb;1.什么是设计模式 &#x1f69a;2.设计模式的优点 &#x1f68d;3.设计模式6大原则 &#x1f6f4;4.设计模式类型 1.什么是设计模式 设计模式代表了最佳的实践&#xff0c;通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开…

Yocto学习笔记1-下载与首次编译

Yocto学习笔记1-下载与首次编译 1、基础环境介绍2、注意点3、安装依赖3.1 yocto常规系统构建所需依赖库&#xff08;较全&#xff09;3.2 龙芯适配时的最小依赖库&#xff08;最小&#xff09; 4、下载4.1 通过git克隆4.2 查看所有远程分支4.3 签出一个长期支持的稳定版本4.4 查…

vue3中ref详解

在Vue 3中&#xff0c;ref是一个核心功能&#xff0c;它允许我们创建响应式引用&#xff0c;这对于处理基本类型数据&#xff08;如字符串、数字等&#xff09;特别有用。ref通过Vue的响应式系统&#xff0c;确保当数据变化时&#xff0c;相关的视图会自动更新。下面将结合代码…

从边缘设备丰富你的 Elasticsearch 文档

作者&#xff1a;David Pilato 我们在之前的文章中已经了解了如何丰富 Elasticsearch 本身和 Logstash 中的数据。 但如果我们可以从边缘设备中做到这一点呢&#xff1f; 这将减少 Elasticsearch 要做的工作。 让我们看看如何从具有代理处理器的 Elastic 代理中执行此操作。 E…

有向图的BFS(c++题解)

题目描述 给定一个有向图&#xff0c;有N个顶点&#xff0c;M条边&#xff0c;顶点从1..N依次编号&#xff0c;求出字典序最小的宽度优先搜索顺序。 输入格式 第1行&#xff1a;2个整数&#xff0c;N&#xff08;1≤N≤200&#xff09;和M&#xff08;2≤M≤5000&#xff09…

Redis如何删除大key

参考阿里云Redis规范 查找大key&#xff1a; redis-cli --bigkeys 1、String类型&#xff1a; Redis 4.0及以后版本提供了UNLINK命令&#xff0c;该命令与DEL命令类似&#xff0c;但它会在后台异步删除key&#xff0c;不会阻塞当前客户端&#xff0c;也不会阻塞Redis服务器的…

【漏洞复现】WordPress Plugin NotificationX 存在sql注入CVE-2024-1698

漏洞描述 WordPress和WordPress plugin都是WordPress基金会的产品。WordPress是一套使用PHP语言开发的博客平台。该平台支持在PHP和MySQL的服务器上架设个人博客网站。WordPress plugin是一个应用插件。 WordPress Plugin NotificationX 存在安全漏洞,该漏洞源于对用户提供的…

MySQL:表的操作

文章目录 创建表查看表结构修改表删除表 前面对于库的操作有了认识后&#xff0c;下面进行表的操作 创建表 以下图为例 创建表其实和定义结构体有点类似&#xff0c;总的来说就是先定义列名&#xff0c;然后后面跟着是列的数据类型&#xff0c;之后在定义结束后可以带上对应的…

【数据挖掘】实验4:数据探索

实验4&#xff1a;数据探索 一&#xff1a;实验目的与要求 1&#xff1a;熟悉和掌握数据探索&#xff0c;学习数据质量分类、数据特征分析和R语言的主要数据探索函数。 二&#xff1a;实验内容 1&#xff1a;数据质量分析 2&#xff1a;统计量分析 3&#xff1a;贡献度分析…

Redis常见数据类型(1)

Redis提供了5种数据结构, 理解每种数据类型的特点对于Redis开发运维非常重要, 同时掌握每种数据类型的常见命令, 会在使用Redis的时候做到游刃有余. 内容如下: 预备知识: 几个全局命令, 数据结构和内部编码, 单线程机制解析. 5种数据类型的特点, 命令使用, 应用场景示例. 键遍历…

uniapp微信小程序_购物车_下单页面

先说下整体逻辑以方便总体理解 1、首先画出下单页面 2、此次画出结算价格页面 3、怎么点击下完单变成结算页面&#xff1f;其实就是把下单页面的信息传递给结算页面就行 问题难点&#xff1f; 点击加号的时候把物品加入一个数组传到下单页面&#xff0c;但是点击的时候不能…

2024-03-24 需求分析-智能问答系统-调研

一. 需求列表 基于本地知识库的问答系统对接外围系统 数字人语音识别二. 待调研的公司 2.1 音视贝 AI智能外呼_大模型智能客服系统_大模型知识库系统_杭州音视贝 (yinshibei.com) 2.2 得助智能 智能AI客服机器人-智能电话机器人客服-电话电销机器人-得助智能 (51ima.com) 2…

数据库中使用IN操作效率问题

1. IN操作的基本概念 IN操作符在SQL中用于指定某个字段的值是否匹配列表中的任何值。这是一个条件操作符&#xff0c;用于在WHERE子句中过滤记录。 SQL语法示例&#xff1a; SELECT * FROM table_name WHERE column_name IN (value1, value2, ...); 2. IN操作的效率问题 当…

关于所谓全能的小讨论

大家好&#xff0c;我是阿赵。   认识我的朋友应该都知道&#xff0c;阿赵我是IT民工&#xff0c;平时上班时间很长。所以我自己是特别珍惜周末的时间的。每次到了周末&#xff0c;我都会做很多事情&#xff0c;基本上是比上班还忙。   我会做的事情很多&#xff0c;比如教…

Debezium日常分享系列之:Debezium2.5稳定版本之MySQL连接器配置示例和Connector参数详解

Debezium日常分享系列之&#xff1a;Debezium2.5稳定版本之MySQL连接器配置示例和Connector参数详解 一、MySQL 连接器配置示例二、添加连接器配置三、连接器属性四、必须的连接器配置属性五、高级 MySQL 连接器配置属性六、Debezium 连接器数据库架构历史配置属性七、用于配置…

LabVIEW比例流量阀自动测试系统

LabVIEW比例流量阀自动测试系统 开发了一套基于LabVIEW编程和PLC控制的比例流量阀自动测试系统。通过引入改进的FCMAC算法至测试回路的压力控制系统&#xff0c;有效提升了压力控制效果&#xff0c;展现了系统的设计理念和实现方法。 项目背景&#xff1a; 比例流量阀在液压…

代码注意事项

1、派生类自定义构造函数需继承父类构造 #include<iostream> using namespace std;class Shape { public: //构造函数&#xff0c;需要释放Shape(int m,int n){s_m m;s_n n;}//虚函数virtual int area()0;//普通成员函数&#xff0c;待会打印void printScreen(){cout&…