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,一经查实,立即删除!

相关文章

奇舞周刊第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 查…

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

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

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…

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

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

使用 chezmoi vscode, 管理你的 dotfiles

什么是 dotfiles In Unix-like operating systems, any file or folder that starts with a dot character (for example, /home/user/.config), commonly called a dot file or dotfile. 任何以 . 开头去命名的文件或者目录都可以称为 dotfile, 在 Unix-like 系统一般用的比较…

Mysql数据库深入理解

目录 一、什么是数据库 二、Mysql基本架构图 1.Mysql客户端/服务器架构 2.客户端与服务器的连接过程 3.服务器处理客户端请求 4.一条查询SQL执行顺序 4.1连接器 4.2查询缓存 4.3解析器 4.4执行器 4.4.1预处理阶段 4.4.2优化阶段 4.4.3执行阶段 5.一条记录如何存…

外包干了4年,技术退步明显.......

先说一下自己的情况&#xff0c;大专生&#xff0c;19年通过校招进入杭州某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落! 而我已经在一个企业干了四年的功能测…

【十六】MySQL数据库设计篇

MySQL数据库设计篇 概述 做服务端开发离不开数据库设计&#xff0c;虽然说服务端技术一直在革新&#xff0c;但是MySQL一直都是我们首选使用的关系型数据库。服务端开发一直以来都是采用数据驱动研发的思想&#xff0c;可见数据库设计是非常重要的&#xff0c;数据库设计的好坏…

01.重新认识文件(Linux基本概念)

知识引入&#xff1a; 我们经常使用word或者wps写的论文、作业等都是文件&#xff0c;而我们这样对文件的认识也比较片面。我们是否思考过&#xff0c;如果文件里面没有写东西&#xff0c;那么计算机会不会保存呢&#xff1f;答案是&#xff0c;肯定会的。因为我们每次建的空文…

Linux下对线程的认识+生产消费者模型+信号量

线程的概念 线程是进程内部中更加轻量化的一种执行流。线程是CPU调度的基本单位&#xff0c;而进程是承担系统资源的实体。就是说一个进程中可能会有多个线程&#xff0c;而在Linux内核中并没有真正重新的创建线程并重新进行资源分配&#xff0c;因为我们每个线程指向的资源都是…

力扣---最长回文子串---二维动态规划

二维动态规划思路&#xff1a; 首先&#xff0c;刚做完这道题&#xff1a;力扣---最长有效括号---动态规划&#xff0c;栈-CSDN博客&#xff0c;所以会有一种冲动&#xff0c;设立g[i]&#xff0c;表示以第i位为结尾的最长回文子串长度&#xff0c;然后再遍历一遍取最大长度即可…

【图解物联网】第8章 物联网与机器人

8.1 由设备到机器人 把我们身边形形色色的“物”都连接到互联网&#xff0c;这就是物联网。物联网中使用的设备随着时间的推移在不断进化&#xff0c;这条进化之路的前方到底有什么呢&#xff1f;这里我们就围绕其中一种形式&#xff0c;即机器人来对讨论一下这个问题。 8.1…