当前位置: 首页 > news >正文

C++项目实战——高性能内存池(一)

目录

一、项目介绍

二、什么是内存池

1、池化技术

2、内存池

三、开胃菜——先设计一个定长的内存池


之前已经学习了很多C++的内容,那么接下来是时候用已学习的内容来做一个项目了,一方面可以巩固所学,另一方面可以将学习的内容结合起来学以致用。

一、项目介绍

当前项目是实现一个高并发内存池,其原型是Google的一个开源项目tcmalloc,tcmalloc全称是Thread-Caching Malloc,即线程缓存的malloc,实现了高效的多线程内存管理,用于替代系统的内存分配相关函数。我们接下来的做法就是将tcmalloc最核心的框架简化后拿出来进行模拟实现出一个自己的高并发内存池,通过这种方式来学习tcmalloc的精华。项目糅合了很多知识内容,包括C/C++、数据结构、操作系统内存管理、单例模式、多线程、互斥锁等等,代码会比较复杂且多个部分相互关联,我会将我理解和做出的内容进行详细地讲解并记录在这里,方便后续的回顾,如果有需要的小伙伴也可以跟着我一起来学习。让我们一起不怕困难,勇往直前!

二、什么是内存池

1、池化技术

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

举个例子,当我们每天早上去上学时都去叫醒爸爸妈妈来领取当天的饭钱,那么爸爸妈妈每天早上都会被我们吵得睡不好觉,那么如果每个月初爸爸妈妈就把当月的所有饭钱给到我们,我们把这些钱放在钱包中,需要时就可以直接方便地进行使用而不需要再去找爸爸妈妈了。

2、内存池

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

三、开胃菜——先设计一个定长的内存池

我们先来尝试实现一个定长的内存池,熟悉一下内存池是如何控制的,同时也为后面的项目内容打下基础

我们首先需要向系统申请一段这样的大块空间,作为内存池,当我们需要进行使用时,直接从这段空间内进行拿取

当我们用完以后,需要返回的内存空间则以链表的形式把空间链接起来,这个链表我们就叫它自由链表

每个结点都为一段内存空间,每个结点中都使用了一个指针的大小来用于存放下一结点的地址,这样就可以链接起来了,那么我们在需要使用空间时,可以先使用被释放回来的链表中的空间,如果链表为空,就去内存池中进行申请空间,如果内存池中的空间不够了,就去系统中再去申请一块大的空间放到内存池。我们可以像图中一样,用两个指针来分别指向两部分,便于操作,这样一来我们可以写出代码,实现一个类

template<class T>
class objectpool
{
public://申请空间T* New(){//指向申请出的空间T* obj = nullptr;//如果自由链表上有内存,优先从自由链表上申请内存if (_freelist){obj = (T*)_freelist;//*(void**)是将void*指针先强转为二级指针,再取地址,就可以得到下一结点的地址_freelist = *(void**)_freelist;}//如果自由链表上没有可申请的空间,则从内存池中进行申请else{//如果内存池不为空,则直接拿取内存使用if (_memory){obj = (T*)_memory;_memory += sizeof(T);}//如果内存池为空,先要从系统中申请空间,再拿取使用else{_memory = (char*)malloc(128 * 1024);obj = (T*)_memory;_memory += sizeof(T);}}return obj;}//释放空间时直接将空间释放到自由链表上进行头插void Delete(T* obj){*(void**)obj = *(void**)_freelist;_freelist = (void*)obj;}private:char* _memory = nullptr;//指向内存池void* _freelist = nullptr;//指向自由链表};

这里我们使用了模板参数,即任何类型的对象都可以使用这个类来申请空间,由于指针的大小在32位系统下为4字节,而在64位系统下为8字节,那么如果该对象的大小较小,作为自由链表中的结点时不足以存放下一结点的指针怎么办?当我们在去内存池申请内存时,如果内存池中依然有空间,但不足以申请出一个对象,那么这里并不能去malloc,也不能使用剩余内存,又该如何呢?

由此看来,上面的这段代码还是有很多问题存在的,那么我们可以根据这些问题来对代码进行优化

一方面,我们可以控制从内存池上申请对象时,将类型的大小与指针大小进行比较,选择大的那个进行申请;另一方面,当内存池的内存不足一个对象的空间大小时就去向系统进行申请一份更大的空间,那么就需要增加一个变量用于存储剩余空间的字节大小

template<class T>
class objectpool
{
public://申请空间T* New(){//指向申请出的空间T* obj = nullptr;//如果自由链表上有内存,优先从自由链表上申请内存if (_freelist){obj = (T*)_freelist;//*(void**)是将void*指针先强转为二级指针,再取地址,就可以得到下一结点的地址_freelist = *(void**)_freelist;}//如果自由链表上没有可申请的空间,则从内存池中进行申请else{size_t objsize = sizeof(T) > sizeof(void*) ? sizeof(T) : sizeof(void*);//如果内存池空间不够,去系统中进行申请if (_remainsize < objsize){_memory = (char*)malloc(128 * 1024);if (_memory == nullptr)throw bad_alloc();_remainsize = 128 * 1024;}obj = (T*)_memory;_memory += objsize;_remainsize -= objsize;}//使用定位new对已经开辟的空间通过显式调用T的构造函数的方法来进行初始化new(obj)T;return obj;}//释放空间时直接将空间释放到自由链表上进行头插void Delete(T* obj){//显式调用析构函数obj->~T();*(void**)obj = *(void**)_freelist;_freelist = (void*)obj;}private:char* _memory = nullptr;//指向内存池void* _freelist = nullptr;//指向自由链表size_t _remainsize = 0;//内存池内剩余字节数
};

对于上面的内容,我们可以写一个程序来进行测试一下,假设有两个数组,定义一下树型结点,数组中的每个元素存放的都是一个结点,其中一个数组中每存放一个元素都使用new对象的方式,而另一个数组则采用我们刚才写出的申请内存的方式

struct TreeNode
{int _val = 0;TreeNode* left = nullptr;TreeNode* right = nullptr;
};
void Testobjpool()
{size_t Round = 5;size_t N = 10000;vector<TreeNode*> v1;v1.reserve(N);size_t begin1 = clock();for (size_t i = 0; i < Round; i++){for (size_t j = 0; j < N; j++){v1.push_back(new TreeNode);}for (size_t j = 0; j < N; j++){delete v1[j];}v1.clear();}size_t end1 = clock();cout << "New cost time" << ":" << end1 - begin1 << endl;vector<TreeNode*> v2;v2.reserve(N);objectpool<TreeNode> objtree;size_t begin2 = clock();for (size_t i = 0; i < Round; i++){for (size_t j = 0; j < N; j++){v2.push_back(objtree.New());}for (size_t j = 0; j < N; j++){objtree.Delete(v2[j]);}v2.clear();}size_t end2 = clock();cout << "objpool cost time" << ":" << end2 - begin2 << endl;
}
int main()
{Testobjpool();
}

通过运行打印可以看出,内存池的方式确实将效率提高了

http://www.dtcms.com/a/342668.html

相关文章:

  • Redis面试精讲 Day 26:Redis源码分析:事件循环与网络模型
  • docker使用和部署深化学习
  • 深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)第一章知识点问答(21题)
  • 华为AUTOSAR质量目标与开发实践
  • LeetCode100 -- Day3
  • 常德二院全栈国产化实践:KingbaseES 数据库的关键作用
  • 机器学习聚类算法学习报告
  • Spring容器Bean的创建流程
  • 使用jwt+redis实现单点登录
  • Matplotlib+HTML+JS:打造可交互的动态数据仪表盘
  • Android - 统一资源标识符 Uri
  • 利用DeepSeek编写调用系统命令用正则表达式替换文件中文本的程序
  • [NCTF2019]True XML cookbook
  • PyTorch Vision 系列:高效数据处理的利器
  • 动手学深度学习(pytorch版):第五章节—深度学习计算(2)参数管理
  • 进程和进程调度
  • Rclone入门对象存储云到云迁移
  • 我从零开始学微积分(2)- 函数与图形
  • YOLO --- YOLOv3以及YOLOv4模型详解
  • Redis Hash数据类型深度解析:从命令、原理到实战场景
  • IPSEC安全基础后篇
  • 易焓仪器安全帽耐熔融金属飞溅性能测试仪:飞溅场景适配与精准检测
  • 力扣 30 天 JavaScript 挑战 第37天 第九题笔记 知识点: 剩余参数,拓展运算符
  • 智慧农业温室大棚远程监控物联网系统解决方案
  • CRaxsRat v7.4:网络安全视角下的深度解析与防护建议
  • AECS(国标ECALL GB 45672-2025)
  • 5G视频终端详解 无人机图传 无线图传 便携式5G单兵图传
  • 汇总图片拖进ps中 photoshop同时打开几个文件夹
  • 【论文阅读 | TCSVT 2025 | CFMW:面向恶劣天气下鲁棒目标检测的跨模态融合Mamba模型】
  • 深入理解Docker网络:从docker0到自定义网络