优化资源利用,用C++内存池点亮编程之路
创作时间:
作者:
@小白创作中心
优化资源利用,用C++内存池点亮编程之路
引用
CSDN
1.
https://m.blog.csdn.net/qq_62989250/article/details/138727763
内存池介绍(Memory Pool)
内存池是一种内存分配方式,通过预先分配和复用内存块。在真正使用内存之前,先申请一大块内存备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。如果我们不需要继续使用当前的内存块了,那么就还给内存池。
为什么要使用内存池
- 内存分配和释放通常涉及系统调用(如
malloc
和free
,或new
和delete
),这些系统调用需要用户态和内核态之间的切换,频繁的系统调用开销较大。内存池是提前分配一块内存,后续我们的内申请都是从内存池中申请,不需要进行系统调用,从而降低了开销。 - 当程序频繁地申请和释放不同大小的内存块时,容易导致内存碎片。
- 传统的内存分配和释放通常涉及到复杂的算法和数据结构(如堆),以及可能的线程同步操作,这些都会消耗较多的CPU时间。而内存池通过预先分配并管理固定大小的内存块,可以大大简化这些操作,从而提高效率。
所以,如果我们需要频繁分配和释放小块内存或者需要大量内存分配和释放,那么建议使用内存池来高效管理内存。
内存池的实现
内存池的相关接口、必要的属性。MemoryPool.h
#ifndef _MEMORY_POOL_H_
#define _MEMORY_POOL_H_
#include <iostream>
#include <string.h>
#include <vector>
#include <mutex>
#define SIZE 1024 * 1024
class MemoryPool{
public:
// 创建一个内存池, 单列模式,因为整个项目只需要一个内存池
static MemoryPool& getInstance(){
// 内存池默认大小是 1G 每块大小是1024字节
static MemoryPool memoryPool_( 1024* SIZE , 1024); // 1024 = 1KB * 1024 = 1mb *1024 = 1gb
return memoryPool_;
}
void *calloc_locate(size_t size); // 分配内存
void delete_locate(void *ptr); // 释放内存
private:
MemoryPool(size_t pool_size , size_t block_size);
~MemoryPool();
void init_memPool(); // 初始化内存池
char *pool_; // 内存池指针
size_t pool_size_; // 内存池的大小
size_t block_size_; // 每一块内存的大小
std::vector<bool>use_block_; // 每个内存块的使用情况
std::mutex mutex_; // 由于内存池是共享资源 , 所以在进行操作时要加锁
};
#endif // _MEMORY_POOL_H_
对相关接口进行实现MemoryPool.cpp
#include "MemoryPool.h"
MemoryPool::MemoryPool(size_t pool_size , size_t block_size){
pool_size_ = pool_size;
block_size_ = block_size;
pool_ = new char[pool_size];
std::cout<<"Memory Start: "<<static_cast<void *>(pool_)<<std::endl;
init_memPool();
}
void MemoryPool::init_memPool(){
// 内存分成的块数 = 内存池的总大小 / 每块的大小
use_block_.resize( pool_size_/block_size_ , false);
}
MemoryPool::~MemoryPool(){
// 对整个内存池资源进行清理
if( pool_ ){
delete[] pool_;
pool_ = nullptr;
pool_size_ = 0;
block_size_ = 0;
use_block_.reserve(0);
}
}
void *MemoryPool::calloc_locate(size_t size){
if( size > block_size_ ){ // 如果我们要分配的内存 ,大于我们每块的大小
return nullptr;
}
std::unique_lock<std::mutex> lock(mutex_);
for(int i = 0 ;i < pool_size_ ;i += block_size_ ){
if( !use_block_[i/block_size_]){ //当前块没有被使用过
use_block_[i/block_size_] = true;
std::cout<<"successful calloc_locate ptr: "<<static_cast<void *>(pool_ + i) <<std::endl;
return pool_ + i ;
}
}
std::cout<<"Failed calloc_locate ptr: "<<std::endl;
// 内存池资源耗尽的情况
return nullptr;
}
void MemoryPool::delete_locate(void *ptr){
if( !ptr ) return ;
if( ptr< pool_ || ptr > pool_ ){ // 需要释放的内存不在我们内存池范围内
return ;
}
std::cout<<"delete ptr: "<<std::hex<<ptr<<std::endl;
// 将指针进行对齐
/*
ptr = 100 -> 需要删除内存的起始位置
pool_ -----> 内存池的起始位置
*/
std::unique_lock<std::mutex>lock;
auto index = (reinterpret_cast<char *>(ptr) - pool_ )/block_size_;
if( index >=0 && index <use_block_.size()){
use_block_[index] = false;
std::cout<<"Delete successful"<<std::endl;
return ;
}
std::cout<<"Delete Failed"<<std::endl;
}
测试demotest.cpp
#include "MemoryPool.h"
int main(int ,char **){
MemoryPool &pool = MemoryPool::getInstance();
// 分配1kb
char *ptr = reinterpret_cast<char *>(pool.calloc_locate(1024));
if(!ptr) return 1;
std::cout<<"ptr: "<< static_cast<void *>(ptr)<<std::endl;
char *ptr2 = reinterpret_cast<char *>(pool.calloc_locate(800));
if(!ptr2) return 1;
std::cout<<"ptr: "<< static_cast<void *>(ptr2)<<std::endl;
pool.delete_locate(ptr);
return 0;
}
结果分析
程序开始,创建一个内存池的单例,并申请一大块内存(1024 x 1024 x 1024)->1GB。每一块的大小是 1024,所以我们的内存池里面,有1024 x 1024 个内存块。vector use_block_会记录每一个内存块的使用情况。如果被使用则标记为true,没有被使用就标记为false。
第一步我们从内存池中分配 1024 个字节,由于我们的每一个块大小是 1024,所以他会返回第一个内存块的起始地址。也就是内存池的起始位置。
第二步,我们再次从内存池中分配800个字节,此时第一个内存块已经被使用,并且每一个块大小是 1024,那么理所当然返回第二个内存块的起始地址。因为第二个内存块已经足够放下了。
数据分析:
第一个内存块返回的起始地址:0x7facb1767010.
第二个内存块返回的起始地址:0x7facb1767410
两者相差400,此时是 16 进制,那么我们将其转换成 10 进制之后是1024为一个内存块的大小
(4 * 16 * 16 -》 1024).
所以我们的内存池完美实现。
热门推荐
如何有效实现港股投资的获利?这种获利方式需要注意哪些问题?
盆栽月季多久浇一次水
月季浇水指南:月季到底应该怎么浇水?
交行净利降1.63%,明年春节前分红
丧葬假一般能请几天?法律法规解析与实务分析
腰带真的那么重要?时尚博主的搭配经验分享。
利用AI大模型打造客服机器人,传统智能客服可以靠边站了
膝关节疼痛分四级,不同时期不同治疗方法,您都知道吗?
黄金突破880元/克!网友直呼“太贵”,转买黄金贴
安全型依附靠6招營造充滿愛和安全感的成長環境 帶來3個好處
基于扩展卡尔曼滤波的多源信息融合室内定位仿真
室内北斗定位系统常用的几种定位方式
垂直画线在设计中的重要性:如何提升视觉效果?
呼叫中心话务员岗位职责:客户服务、信息管理、知识应用与团队协作
葱姜水的功效与作用、禁忌和食用方法
葱姜水的功效与作用、禁忌和食用方法
权志龙:从龙神变龙婶,这些年他到底经历了什么?
贵阳毛坯房价格全解析:市区与郊区的差异及影响因素
职场被领导打压,别硬杠或一味示弱,5招化解困境
探访亚洲最大萤火虫观赏基地:点点萤光照亮世界
洗漱台使用时间怎么调——提高日常生活效率的秘诀
揭秘氧化反应的奥秘
糖友饮食“四部曲”,血糖平稳一整天!(附食谱)
喝酒脸红的人酒量怎么样?
寻找性价比之选:独栋别墅的低成本购买指南
为什么Redis不支持回滚?
红米手机音量键失灵怎么办?三种实用解决方案
醋酸的pH值及其化学性质
什么是凭证式国债以及性质
与婆婆不合导致离婚怎么办