优化资源利用,用C++内存池点亮编程之路

内存池介绍(Memory Pool):

它是一种内存分配方式,通过预先分配和复用内存块。

在真正使用内存之前,先申请一大块内存备用。当有新的内存需求时,就从内存池中分出一部分内存块,

若内存块不够再继续申请新的内存。如果我们不需要继续使用当前的内存块了 ,那么就还给内存池。
在这里插入图片描述

为什么要使用内存池

1、内存分配和释放通常涉及系统调用(如mallocfree | newdelete ),这些系统调用需要用户态和内核态之间的切换,频繁的系统调用开销较大。内存池是提前分配一块内存,后续我们的内申请都是从内存池中申请,不需要进行系统调用,从而降低了开销。

2、当程序频繁地申请和释放不同大小的内存块时,容易导致内存碎片。

3、传统的内存分配和释放通常涉及到复杂的算法和数据结构(如堆),以及可能的线程同步操作,这些都会消耗较多的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;
}

测试demo test.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 个内存块。

vectoruse_block_ 会记录每一个内存块的使用情况
如果被使用则标记为 true , 没有被使用就标记为 false

第一步我们从内存池中分配 1024 个字节,由于我们的每一个块大小是 1024 ,所以他会返回第一个内存块的起始地址。也就是内存池的起始位置。

第二步 ,我们再次从内存池中分配800个字节 , 此时第一个内存块已经被使用 ,并且每一个块大小是 1024,那么理所当然返回第二个内存块的起始地址。因为第二个内存块已经足够放下了。
在这里插入图片描述

数据分析:

第一个内存块返回的起始地址:0x7facb1767010.

第二个内存块返回的起始地址:0x7facb1767410

两者相差400,此时是 16 进制,那么我们将其转换成 10 进制 之后是 1024 为一个内存块的大小
(4 * 16 * 16 - 》 1024).

所以我们的内存池完美实现 。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

OSTE-Web-Log-Analyzer:基于Python的Web服务器日志自动化分析工具

关于OSTE-Web-Log-Analyzer OSTE-Web-Log-Analyzer是一款功能强大的Web服务器日志自动化分析工具&#xff0c;该工具专为安全研究人员设计&#xff0c;能够使用Python Web日志分析工具&#xff08;Python Web Log Analyzer&#xff09;帮助广大研究人员以自动化的形式实现Web服…

【应用浅谈】Odoo的库存计价与产品成本(二)

序言:时间是我们最宝贵的财富,珍惜手上的每个时分 Odoo的库存&#xff08;Stock&#xff09;模块拥有众多功能&#xff0c;其中库存计价是一项非常重要的功能&#xff0c;原生的成本方法分三种&#xff1a;【标准成本】&#xff0c;【平均成本】&#xff0c;【先进先出】&#…

MySQL 身份认证漏洞 CVE-2012-2122

漏洞影响版本 MariaDB versions from 5.1.62, 5.2.12, 5.3.6, 5.5.23 are not.MySQL versions from 5.1.63, 5.5.24, 5.6.6 are not.演示 开启靶场 进入漏洞目录 cd /root/vulhub/mysql/CVE-2012-2122开启漏洞靶场 docker-compose up -d攻击 直接 运行 这个命令 for i i…

验证码生成--kaptcha

验证码生成与点击重新获取验证码 如图所示&#xff0c;本文档仅展示了验证码的生成和刷新显示。 1. 概述 系统通过生成随机验证码图像和文本。 2. 代码分析 2.1. Maven依赖 <dependency><groupId>com.github.penggle</groupId><artifactId>kaptch…

LeetCode - 0088 合并两个有序数组

题目地址&#xff1a;https://leetcode.cn/problems/merge-sorted-array/description/ 引言&#xff1a;话接上回&#xff0c;由于上次面试官着急下班&#xff0c;面试不得不提前终止&#xff0c;这不&#xff0c;他又找我去面试了 面试官&#xff1a;你好&#xff0c;小伙子&a…

(java)websocket服务的两种实现方式

1.基于java注解实现websocket服务器端 1.1需要的类 1.1.1服务终端类 用java注解来监听连接ServerEndpoint、连接成功OnOpen、连接失败OnClose、收到消息等状态OnMessage 1.1.2配置类 把spring中的ServerEndpointExporter对象注入进来 2.1代码示例 2.1.1 maven配置 <…

Docker停止不了

报错信息 意思是&#xff0c;docker.socket可能也会把docker服务启动起来 解决 检查服务状态 systemctl status dockersystemctl is-enabled docker停止docker.socket systemctl stop docker.socket停止docker systemctl stop docker知识扩展 安装了docker后&#xff0c;…

资产公物仓管理系统|实现国有资产智能化管理

1、项目背景 资产公物仓管理系统&#xff08;智仓库DW-S201&#xff09;是一套成熟系统&#xff0c;依托互3D技术、云计算、大数据、RFID技术、数据库技术、AI、视频分析技术对RFID智能仓库进行统一管理、分析的信息化、智能化、规范化的系统。 项目设计原则 方案对公物仓资…

Linux实验 系统管理(三)

实验目的&#xff1a; 了解Linux系统下的进程&#xff1b;掌握一类守护进程——计划任务的管理&#xff1b;掌握进程管理的常用命令&#xff1b;掌握进程的前台与后台管理&#xff1b;了解Linux系统的运行级别&#xff1b;掌握系统服务管理的常用命令。 实验内容&#xff1a; …

小学拼音弄一下

import re from xpinyin import Pinyindef remove_middle_characters(text):# 仅保留汉字chinese_chars re.findall(r[\u4e00-\u9fff], text)cleaned_text .join(chinese_chars)# 如果字符数为偶数&#xff0c;则在中间添加空格if len(cleaned_text) % 2 0:middle_index le…

word-排版文本基本格式

1、文本的基本格式&#xff1a;字体格式、段落格式 2、段落&#xff1a;word排版的基本控制单位 3、每敲一次回车&#xff0c;为一个段落标记&#xff0c;注意区分换行符和段落标记&#xff0c;换行符为指向下的箭头&#xff0c;段落标记为带拐弯的箭头&#xff0c;换行符&…

QT自适应界面 处理高DPI 缩放比界面乱问题

1.pro文件添加 必须添加要不找不到 QT版本需要 5。4 以上才支持 QT widgets 2.main界面提前处理 // 1. 全局缩放使能QApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true);// 2. 适配非整数倍缩放QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::High…

数据结构复习指导之二叉树的概念

文章目录 二叉树 考纲内容 复习提示 1.二叉树的概念 1.1二叉树的定义及其主要特性 1.1.1二叉树的定义 1.1.2几种特殊的二叉树 1.1.3二叉树的性质 1.2二叉树的存储结构 1.2.1顺序存储结构 1.2.2链式存储结构 知识回顾 二叉树 考纲内容 &#xff08;一&#xff09;树…

一件事做了十年

目录 一、背景二、过程1.贫困山区的心理悲哀2.基础差的客观转变3.对于教育的思考4.持续做这件事在路上5.同行人有很早就完成的&#xff0c;有逐渐放弃的&#xff0c;你应该怎么办&#xff1f;6.回头看&#xff0c;什么才是最终留下的东西? 三、总结 一、背景 在哪里出生我们无…

Colab/PyTorch - Getting Started with PyTorch

Colab/PyTorch - Getting Started with PyTorch 1. 源由2. 概要2.1 PyTorch是什么&#xff1f;2.2 为什么学习PyTorch&#xff1f;2.3 PyTorch库概览 3. 步骤4. 预期&展望5. 总结6. 参考资料 1. 源由 世界在发展&#xff0c;为其服务的技术也在不断演变。每个人都要跟上技…

【Linux】动态库与静态库的底层比较

送给大家一句话&#xff1a; 人生最遗憾的&#xff0c;莫过于&#xff0c;轻易地放弃了不该放弃的&#xff0c;固执地坚持了不该坚持的。 – 柏拉图 (x(x_(x_x(O_o)x_x)_x)x) (x(x_(x_x(O_o)x_x)_x)x) (x(x_(x_x(O_o)x_x)_x)x) 底层比较 1 前言2 编译使用比较2 如何加载Than…

一竞技LOL:中韩首场对决暴露TES大问题 BLG和T1的比赛成为焦点!

北京时间5月12日,昨天结束的MSI比赛中第二场比赛是本次MSI第一场中韩大战,由LCK赛区的一号种子GEN战队对阵LPL的二号种子TES战队。TES最终是2:3非常遗憾的输给了Gen,这也意味着TES将要去败者组,本场比赛也是暴露出了TES战队比较大的问题,中单的英雄池以及上单369的状态成为TES战…

enable_shared_from_this使用笔记

解决了&#xff1a; 不能通过原指针增加引用次数的问题 &#xff0c;通过weak_ptr实现。 class MyCar:public std::enable_shared_from_this<MyCar> { public:~MyCar() { std::cout << "free ~Mycar()" << std::endl; } };int main() { MyCar* _…

走进开源,拥抱开源

走进开源&#xff0c;拥抱开源 一、开源文化1.1 什么是开源1.2 为什么要开源1.3 有哪些开源协议 二、选择开源2.1 开源社区的类型与特点2.2 如何选择开源社区2.3 如何选择开源项目 三、参与开源3.1 开源社区的参与方式3.2 开源项目的参与方式 四、Apache Doris 参与示例4.1 Dor…

【iOS】RunLoop详解(二)

RunLoop详解&#xff08;二&#xff09; RunLoop 的概念RunLoop 与线程的关系RunloopRunloop与线程的关系RunLoop对外的接口Runloop的Mode举例说明小结 RunLoop 的内部逻辑RunLoop的底层实现苹果用RunLoop实现的功能AutoreleasePool事件响应手势识别界面更新定时器PerformSelec…