C++内存池的简单实现
作者:Moua 发布时间:2022-05-27 05:20:50
一、内存池基础知识
1、什么是内存池
1.1 池化技术
池化技术是计算机中的一种设计模式,主要是指:将程序中经常要使用的计算机资源预先申请出来,由程序自己管理,程序在使用时直接从“池”中获取,不仅保证了程序占有的资源数量同时减少资源的申请和释放时间。常见的池化技术有内存池、线程池、连接池等。
1.2 内存池
内存池是一种动态内存分配与管理技术。它的核心思想是:预先申请一段内存空间,使用一种高效的数据结构(哈希、链表)进行管理,当程序需要内存时直接从内存池中分配一块内存给程序,同样当使用完时在归还给内存池。这样做的好处是,减少直接使用new/delete、malloc/free等API申请和释放内存的时间,提高程序运行效率;同时,程序每次直接使用new/delete、malloc/free从内存中申请空间,会导致内存碎片问题,内存池直接申请大块内存就减少了内存碎片。
2、内存池的作用
2.1 效率问题
通常申请内存都是通过new/delete、malloc/free接口直接从内存的堆区申请一块内存,释放也是直接释放到堆中。频繁的申请和释放必然消耗大量时间,降低程序的运行效率。
例如:假设每个链表的节点大小为16字节,当链表需要经常插入节点时,必然就需要频繁的内存申请操纵,每次从堆中申请16个字节都要一定的时间开销,释放内存也需要时间开销。使用内存池,我们可以直接从内存中申请“一批节点”,当程序需要内存时不用直接去堆中申请,直接将预先申请好的内存分配给程序。
2.2 内存碎片
频繁的从内存中申请小块内存会导致内存碎片问题。内存碎片分为内碎片和外碎片两种。
1)外碎片
外碎片也就是我们常说的内存碎片。例如:我们每次从内存中申请一块16字节大小的内存,内存中就会存在很多16个字节大小的块,当该内存释放时就可能造成内存碎片,如下图:
内存中空闲内存大小为88字节,但是我们能申请的最大内存块为21字节。
2)内碎片
内碎片是指已经分配出去的内存中存在的未使用的小块内存。内存池技术虽然解决了内存随便但是又造成了内碎片问题,内碎片不可避免但是可以通过程序的优化减少内存内碎片。
例如:实际需要是申请10byte的内存,定长内存池可能会进行内存对齐,一次性分配了16个字节的内存,多余的6字节实际并未使用,这6字节就是内存内碎片。
3、内存池技术的演进
1)最最最最“简单”的内存池
做一个链表,指向空闲的内存。分配就是从链表中取出来一块返回pop,释放就是将内存在push到链表中。需要做好归并,标记和保护,防止内存二次释放问题。
2)定长内存池
实现一个FreeList类,它的本质是一个链表,节点是一块固定大小的内存,采用头插和头删的方式申请释放内存。每个固定内存分配器里面有两个链表:OpenList用于存储未分配的空闲内存对象(FreeList对象),CloseList用于存储已经分配的内存对象。
分配内存就是从IOpenLsit中取出一个对象给程序,释放内存就是将对象push到CloseList里。当内存不够时,OpenList申请一个大块内存在切割成固定的长度大小的小块内存。
3)C++STL库中的内存池
定长内存池存在的问题就是只能申请固定长度的内存,而实际中我们需要申请的内存大小可能是不管固定,在C++STL库中,采用哈希表和定长内存池结合的方式实现了一个内存池。具体如下
构造多个定长内存池,以一个固定的对齐数进行对齐(例如以8字节进行对齐),第一个定长内存池的内存对象大小为8(至少得能保证无论在64位还是32位系统下都可以保存下一个指针类型),第二个内存池对象大小为16...最后一个内存池对象大小为128byte,当申请的内存大小超过128字节时,通过二级空间配置器申请(直接从内存中申请)。
构造一个哈希表,将不同大小的内存对象挂在哈希表中。如下图:
申请内存:加入要申请的内存大小为8字节直接在index = 0处分配一块内存,当然申请的内存小于8字节时也会直接分配8字节的内存。当Free_list[index]为nullptr时从内存中申请一块内存,切割成固定大小‘挂在'Free_list[index]位置。
释放内存:根据内存对象大小,计算index在插入到哈希表中的index位置。
二、简易内存池原理
1、整体设计
1.1 内存池结构
两个链表,RequestMemory和ReleaseMemory。
RequestMemory链表存储的是使用new或者malloc从物理内存申请的还没有被使用的内存块,是一个个的memNode节点。
ReleaseMemory链表存储的是使用完释放回来的固定大小的内存块。
1.2 申请内存
先在ReleaseMemory找,如果有内存则直接pop使用
ReleaseMemory为nullptr时,在RequestMemory中找。
RequestMemory的头节点表示的是新申请的,申请内存时只需要在头结点中找,判断头结点的useCount和sumCount是否相等。当useCount等于sumCount时表示已经用完了,就需要去物理内存中申请,否则直接从表头push一块。
去物理内存申请内存时,申请的大小是上一次申请内存块大小的二倍,并将申请的内存块push到RequestMemory头部。
1.3 释放内存
释放内存时,直接将要释放的内存push到ReleaseMemory的头部即可。
2、详细剖析
2.1 blockNode结构
blockNode表示一个个新申请的内存块,用一个结构体进行管理。blockNode成员如下:
void* _memory:表示新申请的内存块的首地址
BlockNode * _next:存储next节点
_objNum:内存块对象的个数
注意:blockNode的大小每次都是上一次的二倍,是一个质数增长,因此应该设置一个上限,当到达一定大小后进行线性增长。这里规定,最大内存块的大小为100000*sizeof(T),T表示的是申请的节点类型。
2.2 单个对象的大小
这里的单个对象指的ReleaseMemory的节点大小,当用户申请的内存大小sizeof(T)小于sizeof(T*)时,为了能够将该对象链接到ReleaseMemory中,应该按照T*进行分配。
3、性能比较
分别使用malloc/free、new/delete、memPool申请和释放110000个内存,时间如下:
三、简易内存池完整源码
#include<iostream>
#include<vector>
#include<ctime>
using namespace std;
template<class T>
class MemPool
{
private:
//内存块结构
typedef struct BlockNode
{
void* _memory;//内存块地址
BlockNode* _next;//下一个blockNode
size_t _objNum;//内存块对象的个数
//构造函数---num表示申请对象的个数
BlockNode(size_t num)
:_objNum(num),
_next(nullptr)
{
_memory = malloc(_objNum*_size);
}
~BlockNode()
{
free(_memory);
_memory = nullptr;
_next = nullptr;
_objNum = 0;
}
}BlockNode;
protected:
static size_t _size;//单个对象的大小
T* _releaseMemory = nullptr;//释放的内存
BlockNode* _requestMemory;//申请的内存块
size_t _maxNum;//内存块最大的大小
size_t _useCount;//当前内存块已经使用的对象个数
protected:
//设置单个对象的大小
static size_t setSize()
{
return (sizeof(T) >= sizeof(T*) ? sizeof(T):sizeof(T*));
}
public:
MemPool()
:_useCount(0),
_releaseMemory(nullptr),
_maxNum(100000*_size)
{
//开始先申请32个_size大小的空间
_requestMemory = new BlockNode(32);
}
~MemPool()
{
BlockNode *cur = _requestMemory;
while (cur)
{
BlockNode* del = cur;
cur = cur->_next;
delete del; //会自动调用~BlockNode()
}
}
T* New()
{
//先在releaseMemory中找
if (_releaseMemory)
{
T* obj = _releaseMemory;
_releaseMemory = *((T**)_releaseMemory);//releaseMemory的前几个字节存储的是下一个节点的地址
return obj;
}
else
{
//判断requesetMemory中是否还有空闲内存
if (_requestMemory->_objNum == _useCount)
{
//取物理内存中申请一块内存
size_t size = 2 * _useCount >= _maxNum ? _maxNum : 2 * _useCount;
BlockNode* newBlock = new BlockNode(size);
newBlock->_next = _requestMemory;
_requestMemory = newBlock;
_useCount = 0;
}
//走到这里,一定有内存
T* obj = (T*)((char*)_requestMemory->_memory+_useCount*_size);
_useCount++;
return new(obj)T();//用定位new对这块空间初始化
}
}
void Delete(T* obj)
{
if (obj)
{
obj->~T();
*((T**)obj) = _releaseMemory;
_releaseMemory = obj;
}
}
};
//静态成员变量,类外初始化
template<typename T>
size_t MemPool<T>::_size = MemPool<T>::setSize();
struct TreeNode
{
int _val;
TreeNode* _left;
TreeNode* _right;
};
void test1()
{
MemPool<TreeNode> mp;
vector<TreeNode*> v;
for (int i = 0; i < 10; i++)
{
TreeNode* mem = mp.New();
v.push_back(mem);
}
for (int i = 0; i < 10; i++)
{
mp.Delete(v[i]);
}
}
来源:https://blog.csdn.net/qq_47406941/article/details/118388207
猜你喜欢
- 话不多说,请看实例代码String ip = request.getHeader("x-forwarded-for");
- 1、JDBCJDBC 就是 数据库开发 操作的 代名词,因为只要是现代商业项目的开发那么一定是离不开 数据库 的,不管你搞的是什么,只要是想
- 最近有很多小伙伴给我留言,分布式系统时代,线程并发,资源抢占,"锁" 慢慢变得很重要。那么常见的锁都有哪些?今天Tom哥
- 说到内存管理,笔者这里想先比较一下java与C、C++之间的区别:在C、C++中,内存管理是由程序员负责的,也就是说程序员既要完成繁重的代码
- Java 读取外部资源的方法详解在Java代码中经常有读取外部资源的要求:如配置文件等等,通常会把配置文件放在classpath下或者在we
- 一、新时间日期API常用、重要对象介绍ZoneId: 时区ID,用来确定Instant和LocalDateTime互相转换的规则Instan
- 简介Springboot Admin是一个管理和监控Springboot项目的组件,分为服务端和客户端,两端通过http进行通信。由于其轻量
- 前言Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端 负载均衡的工具。(负载均衡+RestTempl
- Jetty和tomcat的比较Tomcat和Jetty都是一种Servlet引擎,他们都支持标准的servlet规范和JavaEE的规范。架
- 1.分页类package org.zh.basic;/** * 页面类 * * @author keven&
- 开发过程, 我们习惯把数据源配置, 项目常量, 日志配置等基础数据配置写到一个个单独的的文件中. 如jdbc.properties等各种.格
- 整理文档,搜刮出一个C# 通过 oledb 操作Excel实例代码,稍微整理精简一下做下分享。public string GetConnec
- Java调用cmd命令,并输出显示信息:package com.anxin.cmd.test; import java.io.Buffere
- 在开发Android应用程序中,经常会自定义View来实现各种各样炫酷的效果,在实现这吊炸天效果的同时,我们往往会定义很多attr属性,这样
- 本文实例讲述了C#使用NPOI导入Excel的方法。分享给大家供大家参考,具体如下:NPOI是由国人开发的一个进行excel操作的第三方库。
- 一,在一个公共类里创建一个公共方法,然后需要验证的页面都调用这个方法 //在此例子中,就是在入口函数里调用CheckLogin()
- 1. 关于POJO类属性为基本类型存在的问题在项目开发中遇到的问题,定义POJO类的时候有些属性定义为了基本数据类型,比如long,shor
- 概述:Flutter中常用的滑动布局 ScrollView 有 SingleChildScrollView、NestedScrollView
- 本文实例讲述了Android开发实现横向列表GridView横向滚动的方法。分享给大家供大家参考,具体如下:Android 横向列表实现,可
- 本文实例讲述了JAVA快速排序实现方法。分享给大家供大家参考,具体如下:package com.ethan.sort.java;import