C++项目-- 高并发内存池(一)

C++项目-- 高并发内存池(一)

文章目录

  • C++项目-- 高并发内存池(一)
  • 一、项目介绍
    • 1.项目来源
    • 2.内存池介绍
      • 1.池化技术
      • 2.内存池
      • 3.内存池主要解决的问题
      • 4.malloc
  • 二、定长内存池
    • 1.定长内存池的设计
    • 2.代码实现
    • 3.性能测试
    • 4.直接在堆上申请空间
  • 三、要点


一、项目介绍

1.项目来源

当前项目是实现一个高并发的内存池,他的原型是google的一个开源项目tcmalloc,tcmalloc全称Thread-Caching Malloc,即线程缓存的malloc,实现了高效的多线程内存管理,用于替代系统的内存分配相关的函数(malloc、free),Go语言直接用它做了自己内存分配器。
本项目是把tcmalloc最核心的框架简化后拿出来,模拟实现出一个自己的高并发内存池,目的就是学习tcamlloc的精华。

tcmalloc源码地址

2.内存池介绍

1.池化技术

所谓“池化技术”,就是程序先向系统申请过量的资源,然后自己管理,以备不时之需。之所以要申请过量的资源,是因为每次申请该资源都有较大的开销,不如提前申请好了,这样使用时就会变得非常快捷,大大提高程序运行效率。
在计算机中,有很多使用“池”这种技术的地方,除了内存池,还有连接池、线程池、对象池等。以服务器上的线程池为例,它的主要思想是:先启动若干数量的线程,让它们处于睡眠状态,当接收到客户端的请求时,唤醒池中某个睡眠的线程,让它来处理客户端的请求,当处理完这个请求,线程又进入睡眠状态。

2.内存池

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

3.内存池主要解决的问题

内存池主要解决的当然是效率的问题,其次如果作为系统的内存分配器的角度,还需要解决一下内存碎片的问题。那么什么是内存碎片呢?
在这里插入图片描述
再需要补充说明的是内存碎片分为外碎片和内碎片上面我们讲的是外碎片问题

  • 外部碎片是一些空闲的连续内存区域太小,这些内存空间不连续,以至于合计的内存足够,但是不能满足一些的内存分配申请需求。
  • 内部碎片是由于一些对齐的需求,导致分配出去的空间中一些内存无法被利用。内碎片问题,我们后面项目就会看到,那会再进行更准确的理解。

4.malloc

C/C++中我们要动态申请内存都是通过malloc去申请内存,但是我们要知道,实际我们不是直接去堆获取内存的,而malloc就是一个内存池malloc() 相当于向操作系统“批发”了一块较大的内存空间,然后“零售”给程序用。当全部“售完”或程序有大量的内存需求时,再根据实际需求向操作系统“进货”
malloc的实现方式有很多种,一般不同编译器平台用的都是不同的。比如windows的vs系列用的微软自己写的一套,linux gcc用的glibc中的ptmalloc。

一文了解,Linux内存管理,malloc、free 实现
malloc()背后的实现原理——内存池
malloc的底层实现(ptmalloc)

二、定长内存池

作为程序员(C/C++)我们知道申请内存使用的是malloc,malloc其实就是一个通用的大众货,什么场景下都可以用,但是什么场景下都可以用就意味着什么场景下都不会有很高的性能,下面我们就先来设计一个定长内存池做个开胃菜,当然这个定长内存池在我们后面的高并发内存池中也是有价值的,所以学习他目的有两层,先熟悉一下简单内存池是如何控制的,第二他会作为我们后面内存池的一个基础组件。
在这里插入图片描述
在这里插入图片描述

1.定长内存池的设计

  • 解决固定大小的内存申请和释放需求
  • 特点:
    • 性能达到极致
    • 不考虑内存碎片问题
      在这里插入图片描述
  • 使用char*指针指向内存池的首地址,方便以字节长度管理内存;
  • 增加freeList机制,用于管理归还的内存块,而不是直接归还到内存池

2.代码实现

  • 可以用非类型模板参数构建定长内存池,传入的模板参数N就是内存池的大小
    在这里插入图片描述

  • 也可以使用类型模板参数,由于类型的大小是固定的,因此每次申请的内存大小也是固定的
    为了配合后面的高并发内存池,选择这种方案
    在这里插入图片描述

  • 成员变量:

    • _memory:指向未分配内存的指针,指向的是内存的地址,定义为void类型不好进行++操作,定义为char类型方便进行字节操作
    • _freeList:自由链表用来管理归还回来的内存块,每个内存块都是一个节点,前一个内存块的前四个字节存储下一个内存块的地址
      在这里插入图片描述
    • _remainBytes:剩余内存空间的字节数
  • New函数:用来为对象申请内存空间
    在这里插入图片描述

    • 第一次使用,初始化申请内存空间,报错抛异常
    • 分配对象空间,对象的指针指向_memory,_memory向后移动
    • 剩余内存空间不足,需要重新申请空间(当剩余空间不足以开辟一个T对象的时候,就需要申请空间了 )
    • freeList里面的空间是归还的空间,也是可以用于空间申请的,因此在freeList不为空时,优先将其中的内存块重复利用,开辟出去的内存块使用链表的头删
    • 由于内存块在delete的时候需要存储其他内存块的指针,因此一个内存块的大小不能小于当前系统指针的大小
    • 如果T是自定义类型,使用New只开了空间,并没有初始化,可以对一块已经有的空间调用构造函数进行初始化,就是定位new
  • Delete函数:用于处理还回来的对象的内存空间
    在这里插入图片描述

    • 将还回来的对象加入到freelist中
      在这里插入图片描述
    • _freeList存储的是第一个内存块的地址,第一个内存块的头4个字节用于存储下一个内存块的地址将obj强转成int*类型,再解引用,就可以完成对该内存块头四字节的访问
      在这里插入图片描述
    • (重点)在32位系统下,地址是4字节,但是64位系统下地址是8字节,可以通过将obj指针强转成void**类型,再解引用,这样obj访问的就是void*的大小,而void*是指针,其大小正取决于操作系统的位数,这样在32位系统下,void*为4字节,64位系统下,void*为8字节(重点)
    • 第二个内存块插入链表使用头插法,减少时间复杂度
      在这里插入图片描述
      这样就不用区分freeList是否为空了,都是一样的操作
    • 需要显式调用析构函数完成对象的清理

ObjectPool.h

#pragma once
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
using std::vector;template<class T>
class ObjectPool {
public:T* New() {T* obj = nullptr;//若freeList不为空,先分配这里的空间if (_freeList) {void* next = *((void**)_freeList);obj = (T*)_freeList;_freeList = next;}else {if (_remainBytes < sizeof(T)) { //当剩余空间不足一个对象时,就需要重新申请空间//这其中也包括了首次申请空间_remainBytes = 128 * 1024;_memory = (char*)malloc(_remainBytes);if (_memory == nullptr) {throw std::bad_alloc();}}//为新对象分配内存空间obj = (T*)_memory;//一个内存块的大小不能小于当前系统指针的大小,因为freeList需要存指针int objSize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);_memory += objSize;_remainBytes -= objSize;}//自定义类型只开辟了空间,并没有初始化,定位new,显式调用类的构造函数new(obj)T;return obj;}void Delete(T* obj) {//显式调用类的析构函数obj->~T();//头插*((void**)obj) = _freeList;_freeList = obj;}private:char* _memory = nullptr;void* _freeList = nullptr;int _remainBytes = 0;
};

3.性能测试

  • 分别使用new和定长内存池去申请和释放N次资源,一共3轮,分别记录时间
  • 在release版本下测试
#include "ObjectPool.h"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;
}int main() {TestObjectPool();return 0;
}

在这里插入图片描述
可以看出定长内存池的速度优势很明显;

4.直接在堆上申请空间

我们可以使用系统接口,绕过malloc,直接在堆上申请空间;

  • windows使用VirtualAlloc函数来直接在堆上获取内存空间
  • linux下使用brk mmap等
#pragma once
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
using std::vector;#ifdef _WIN32#include<windows.h>
#else#endif//直接去堆上申请空间
inline static void* SystemAlloc(size_t kpage) {
#ifdef _WIN32void* ptr = VirtualAlloc(0, kpage << 13, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else#endif // _WIN32if (ptr == nullptr) {throw std::bad_alloc();}return ptr;
}template<class T>
class ObjectPool {
public:T* New() {T* obj = nullptr;//若freeList不为空,先分配这里的空间if (_freeList) {void* next = *((void**)_freeList);obj = (T*)_freeList;_freeList = next;}else {if (_remainBytes < sizeof(T)) { //当剩余空间不足一个对象时,就需要重新申请空间//这其中也包括了首次申请空间_remainBytes = 128 * 1024;//_memory = (char*)malloc(_remainBytes);_memory = (char*)SystemAlloc(_remainBytes >> 13); // 直接在堆上申请空间if (_memory == nullptr) {throw std::bad_alloc();}}//为新对象分配内存空间obj = (T*)_memory;//一个内存块的大小不能小于当前系统指针的大小,因为freeList需要存指针int objSize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);_memory += objSize;_remainBytes -= objSize;}//自定义类型只开辟了空间,并没有初始化,定位new,显式调用类的构造函数new(obj)T;return obj;}void Delete(T* obj) {//显式调用类的析构函数obj->~T();//头插*((void**)obj) = _freeList;_freeList = obj;}private:char* _memory = nullptr;void* _freeList = nullptr;int _remainBytes = 0;
};

在这里插入图片描述

三、要点

  1. 当剩余空间不足一个对象时,就需要重新开辟空间;
  2. 开辟空间时,首先考虑将freeList中已经归还的空间再次分配出去;
  3. freeList使用头插和头删,降低时间复杂度;
  4. 使用*((void**)obj)的方式,来自动适配不同位的系统下指针的大小

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

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

相关文章

【考研408】算法与数据结构笔记

文章目录 绪论数据结构的基本概念算法和算法评价 线性表线性表的定义和基本操作线性表的顺序表示线性表的链式表示 栈和队列栈基本操作栈的顺序存储结构栈的链式存储 队列队列常见的基本操作队列的顺序存储结构队列的链式存储结构双端队列 栈和队列的应用栈在括号匹配中的应用栈…

【内置对象·js】

数学对象 document.write("圆周率为 " Math.PI "<br>");日期对象 var date new Date(); // 实例化 Date 对象var month date.getMonth() 1; // 获取月份&#xff0c;取值为 0&#xff08;一月&#xff09;到 11&#xff08;十二月&#xff09;之…

SQL注入:sqli-labs靶场通关(1-37关)

SQL注入系列文章&#xff1a; 初识SQL注入-CSDN博客 SQL注入&#xff1a;联合查询的三个绕过技巧-CSDN博客 SQL注入&#xff1a;报错注入-CSDN博客 SQL注入&#xff1a;盲注-CSDN博客 SQL注入&#xff1a;二次注入-CSDN博客 ​SQL注入&#xff1a;order by注入-CSDN博客 …

【Go语言成长之路】安装Go

文章目录 安装Go一、下载Go语言安装包二、删除以前安装的Go版本三、添加/usr/local/go/bin到环境变量内四、确认安装成功 安装Go Note: 这里只演示安装Linux版本的Go&#xff0c;若为其它版本&#xff0c;请按照官网的安装教程进行安装即可。 一、下载Go语言安装包 ​ 在浏览…

【IM】长连接网关设计探索(一)

目录 1.长连接网关的必要性2. 设计目标2.1 技术挑战2.2 技术目标 3. 方案选型3.1 网关IP地址的选择3.1.1 使用httpDNS服务3.1.2 自建http server作为IP config server3.1.3 最佳方案 3.2 高并发收发设计3.2.1 C10K问题3.2.2 方案探索双协程监听channel实现全双工 一个定时器 1…

99 C++内存高级话题。new/delete的进一步认识 整理

1. new 初始化的整理。 class Teacher120 { public:Teacher120() {cout << "teacher120 moren 构造函数" << endl;}Teacher120(int age):m_age(m_age) {cout << "teacher120 构造函数" << endl;}~Teacher120() {cout << &qu…

hivesql的基础知识点

目录 一、各数据类型的基础知识点 1.1 数值类型 整数 小数 float double(常用) decimal(针对高精度) 1.2 日期类型 date datetime timestamp time year 1.3 字符串类型 char varchar / varchar2 blob /text tinyblob / tinytext mediumblob / mediumtext lon…

CentOS7虚拟机设置静态IP

虚拟机上ip是有时效性的&#xff0c;过期后会自动更换&#xff0c;因此如果想让ip不变&#xff0c;就得手动设置静态ip。 第一步&#xff1a;先查看主机的子网掩码 1.1、windows命令ipconfig&#xff0c;如下图&#xff1a; 第二步&#xff1a;查看虚拟机的网关、ip区间的设…

单臂路由实验(华为)

思科设备参考&#xff1a; 单臂路由实验&#xff08;思科&#xff09; 一&#xff0c;实验目的 在路由器的一个接口上通过配置子接口的方式&#xff0c;实现相互隔离的不同vlan之间互通。 ​ 二&#xff0c;设备配置 Switch1 <Huawei>sys [Huawei]vlan batch 10 20…

【C++】类与对象(三)—运算符重载|const成员函数|取地址及const取地址操作符重载

前言 运算符重载&#xff0c;自增自减运算符重载&#xff0c;const成员函数&#xff0c;取地址及const取地址操作符重载 文章目录 一、运算符重载自增和自减运算符重载 二、const 成员函数三、取地址及const取地址操作符重载&#xff08;了解即可&#xff09; 一、运算符重载 运…

【MySQL】深入理解隔离性

深入理解隔离性 一、数据库并发的场景二、多版本并发控制&#xff08; MVCC &#xff09;三、三个前提知识1、3个记录隐藏字段2、undo日志 四、快照的概念五、Read View六、隔离级别RR与RC的本质区别 一、数据库并发的场景 数据库并发的场景总共有三种&#xff1a; 读-读&…

JVM中一次完整的GC回收流程

JVM堆内存结构简述 JVM堆内存结构图 堆初体验 所有的对象实例以及数组都要在堆上分配&#xff0c;堆是垃圾收集器管理的主要区域&#xff0c;也被称为“GC 堆”&#xff0c;也是我们优化最多考虑的地方。因为在一个项目中&#xff0c;会不断地创建对象&#xff0c;都是在堆里…

DevOps 教程 (4) - CI/CD 整合

在本第四章的"DevOps 教程"系列中&#xff0c;我们将介绍CI/CD整合的概念和实践。我们会介绍DevOps所带来的好处&#xff0c;包括团队协作、开发效率和产品交付速度的显著提升。 我们还将讨论在DevOps中的不同角色&#xff0c;并理解每个角色在持续集成和持续交付中的…

微调实操一: 增量预训练(Pretraining)

1、前言 《微调入门篇:大模型微调的理论学习》我们对大模型微调理论有了基本了解,这篇结合我们现实中常见的场景,进行大模型微调实操部分的了解和学习,之前我有写过类似的文章《实践篇:大模型微调增量预训练实践(二)》利用的MedicalGPT的源码在colab进行操作, 由于MedicalGPT代…

浅压缩、深压缩、双引擎、计算机屏幕编码……何去何从?

专业视听领域尤其显示控制和坐席控制领域&#xff0c;最近几年最激动人心的技术&#xff0c;莫过于分布式了。 分布式从推出之日就备受关注&#xff1a;担心稳定性的&#xff0c;质疑同步性能的&#xff0c;怀疑画面质量的…… 诚然&#xff0c;我们在此前见多了带着马赛克的…

【C++】类和对象1:类的定义、访问限定符、作用域及对象大小

前言 本文主要是简单的介绍一下类是什么、如何使用 类的定义 class className { // 类体&#xff1a;由成员函数和成员变量组成 };// 一定要注意后面的分号class为定义类的关键字&#xff0c;ClassName为类的名字&#xff0c;{}中为类的主体&#xff0c;注意类定义结束时后面…

智慧文旅:驱动文化与旅游融合发展的新动力

随着科技的快速发展和人们生活水平的提高&#xff0c;文化和旅游的融合成为了时代发展的必然趋势。智慧文旅作为这一趋势的引领者&#xff0c;通过先进的信息技术手段&#xff0c;推动文化与旅游的深度融合&#xff0c;为产业的发展注入新的活力。本文将深入探讨智慧文旅如何成…

【制作100个unity游戏之23】实现类似七日杀、森林一样的生存游戏9(附项目源码)

本节最终效果演示 文章目录 本节最终效果演示系列目录前言回收物品素材绘制UI代码控制垃圾桶回收功能效果 源码完结 系列目录 前言 欢迎来到【制作100个Unity游戏】系列&#xff01;本系列将引导您一步步学习如何使用Unity开发各种类型的游戏。在这第23篇中&#xff0c;我们将…

低成本高效益,电子画册才是品牌的重要选择

​随着互联网的普及和数字化技术的进步&#xff0c;电子画册已成为许多品牌的重要选择。与传统印刷画册相比&#xff0c;电子画册具有低成本、高效益的优点&#xff0c;成为品牌宣传的新趋势。 具体来说&#xff0c;电子画册可以通过在线平台或移动设备轻松查看&#xff0c;无需…

logback自定义生成DB日志(java环境)

目的&#xff1a; 未来在生成日志写入数据库中加一个特殊的字段&#xff0c;官方老版本提供的DBAppender无法实现&#xff0c;并且好巧不巧&#xff0c;在新版本这个实现也被删除了&#xff0c;所以重写一个实现。 1. 安装依赖 安装logback maven依赖 注意&#xff1a; lo…