C++ 内存池(Memory Pool)详解

1. 基本概念

内存池是一种内存管理技术,旨在提高内存分配的效率。它通过预先分配一块大的内存区域(池),然后从中分配小块内存来满足应用程序的需求。这样可以减少频繁的内存分配和释放带来的性能开销。

2. 设计思路

内存池的设计通常遵循以下步骤:

  • 预分配内存:在程序开始时,预先分配一块较大的内存区域。
  • 管理空闲块:使用链表、栈或数组等数据结构管理可用内存块。
  • 分配和释放:提供分配和释放接口,让用户从内存池中获取和释放内存。
  • 回收机制:当内存块被释放时,将其返回到内存池中,便于后续使用。

3. 原理

内存池的核心原理是降低内存分配的时间复杂度。标准的 newdelete 操作在需要内存时会与操作系统频繁交互,可能会造成较大的开销。而内存池将这种频繁操作集中到池的初始化阶段,后续的分配和释放则在池内进行,速度更快。

4. 使用场景

内存池适用于以下场景:

  • 游戏开发:频繁创建和销毁对象,例如子弹、敌人等。
  • 高性能计算:实时系统对内存分配速度的高要求。
  • 网络编程:处理大量小数据包时,内存池可以提高性能。
  • 嵌入式系统:资源有限的环境中,避免频繁的动态内存分配。

5. 详细讲解

  • 内存池的优势

    • 性能提升:通过减少系统调用,提高内存分配和释放的速度。
    • 内存碎片减少:通过统一管理,减少内存碎片的问题。
    • 简化内存管理:可以设计为自动回收机制,降低内存泄漏的风险。
  • 内存池的劣势

    • 内存浪费:如果分配的块未被充分利用,可能会造成内存浪费。
    • 复杂性增加:需要额外的代码管理内存池,增加了系统的复杂性。
  • 扩展功能

    • 多线程支持:在多线程环境中,可以使用锁或无锁队列管理内存池。
    • 调试功能:可以在分配和释放时记录堆栈信息,便于调试内存泄漏。

6. 场景示例

内存池的详细实现

1. 内存池类的结构

内存池主要由以下几个部分构成:

  • 内存块:固定大小的内存单元。
  • 内存池管理:负责分配和释放内存块。
  • 空闲块管理:使用链表或栈来管理未使用的内存块。
2. 经典的内存池实现

#include <iostream>
#include <vector>
#include <stdexcept>
#include <cassert>

class MemoryPool {
public:
    // 构造函数,初始化内存池
    MemoryPool(size_t blockSize, size_t blockCount)
        : m_blockSize(blockSize), m_blockCount(blockCount), m_usedBlocks(0) {
        // 分配内存池
        m_pool = malloc(blockSize * blockCount);
        if (!m_pool) {
            throw std::bad_alloc(); // 内存分配失败,抛出异常
        }
        // 初始化空闲块列表
        for (size_t i = 0; i < blockCount; ++i) {
            m_freeBlocks.push_back(static_cast<char*>(m_pool) + i * blockSize);
        }
    }

    // 析构函数,释放内存池
    ~MemoryPool() {
        free(m_pool);
    }

    // 分配内存块
    void* allocate() {
        // 如果没有可用的块,返回nullptr
        if (m_freeBlocks.empty()) {
            return nullptr; // 可以改进为扩展内存池
        }
        // 从空闲块列表中取出一个块
        void* block = m_freeBlocks.back();
        m_freeBlocks.pop_back(); // 从空闲列表中移除
        m_usedBlocks++; // 增加使用计数
        return block; // 返回分配的块
    }

    // 释放内存块
    void deallocate(void* block) {
        assert(block != nullptr); // 确保要释放的块不是nullptr
        m_freeBlocks.push_back(static_cast<char*>(block)); // 添加到空闲块列表
        m_usedBlocks--; // 减少使用计数
    }

    // 获取当前使用的块数
    size_t usedBlocks() const {
        return m_usedBlocks;
    }

    // 获取空闲块的数量
    size_t freeBlocks() const {
        return m_freeBlocks.size();
    }

private:
    size_t m_blockSize;            // 每个内存块的大小
    size_t m_blockCount;           // 内存池中的块数量
    void* m_pool;                  // 内存池的起始地址
    std::vector<void*> m_freeBlocks; // 存储空闲块的列表
    size_t m_usedBlocks;           // 当前使用的块数
};

// 示例使用
int main() {
    const size_t BLOCK_SIZE = 32; // 每个块32字节
    const size_t BLOCK_COUNT = 10; // 总共10个块

    MemoryPool pool(BLOCK_SIZE, BLOCK_COUNT); // 创建内存池

    // 分配内存块
    void* block1 = pool.allocate();
    void* block2 = pool.allocate();

    std::cout << "Allocated blocks: " << block1 << ", " << block2 << std::endl;
    std::cout << "Used blocks: " << pool.usedBlocks() << std::endl;
    std::cout << "Free blocks: " << pool.freeBlocks() << std::endl;

    // 释放内存块
    pool.deallocate(block1);
    pool.deallocate(block2);

    std::cout << "After deallocation:" << std::endl;
    std::cout << "Used blocks: " << pool.usedBlocks() << std::endl;
    std::cout << "Free blocks: " << pool.freeBlocks() << std::endl;

    return 0;
}

3. 详细讲解
3.1 内存池的工作原理
  • 初始化:在创建内存池时,预先分配一大块内存,分成多个固定大小的内存块。
  • 分配:当请求内存时,从空闲块列表中取出一个块并返回。如果没有空闲块,可以考虑扩展内存池。
  • 释放:释放时将内存块返回到空闲块列表中,便于后续使用。
3.2 主要功能说明
  • allocate():从空闲块中分配一个块,返回其地址;如果没有可用块,返回 nullptr
  • deallocate():将已使用的内存块返回到空闲块列表中。
  • usedBlocks()freeBlocks():分别返回当前使用的块数和空闲块数,便于监控内存使用情况。
4. 扩展使用示例
4.1 用于游戏对象管理

假设我们有一个游戏中的子弹对象,我们可以使用内存池来管理这些对象的创建和销毁。

#include <iostream>
#include <vector>
#include <stdexcept>
#include <cassert>

class MemoryPool {
public:
    // 构造函数,初始化内存池
    MemoryPool(size_t blockSize, size_t blockCount)
        : m_blockSize(blockSize), m_blockCount(blockCount), m_usedBlocks(0) {
        // 分配内存池
        m_pool = malloc(blockSize * blockCount);
        if (!m_pool) {
            throw std::bad_alloc(); // 内存分配失败,抛出异常
        }
        // 初始化空闲块列表
        for (size_t i = 0; i < blockCount; ++i) {
            m_freeBlocks.push_back(static_cast<char*>(m_pool) + i * blockSize);
        }
    }

    // 析构函数,释放内存池
    ~MemoryPool() {
        free(m_pool);
    }

    // 分配内存块
    void* allocate() {
        // 如果没有可用的块,返回nullptr
        if (m_freeBlocks.empty()) {
            return nullptr; // 可以改进为扩展内存池
        }
        // 从空闲块列表中取出一个块
        void* block = m_freeBlocks.back();
        m_freeBlocks.pop_back(); // 从空闲列表中移除
        m_usedBlocks++; // 增加使用计数
        return block; // 返回分配的块
    }

    // 释放内存块
    void deallocate(void* block) {
        assert(block != nullptr); // 确保要释放的块不是nullptr
        m_freeBlocks.push_back(static_cast<char*>(block)); // 添加到空闲块列表
        m_usedBlocks--; // 减少使用计数
    }

    // 获取当前使用的块数
    size_t usedBlocks() const {
        return m_usedBlocks;
    }

    // 获取空闲块的数量
    size_t freeBlocks() const {
        return m_freeBlocks.size();
    }

private:
    size_t m_blockSize;            // 每个内存块的大小
    size_t m_blockCount;           // 内存池中的块数量
    void* m_pool;                  // 内存池的起始地址
    std::vector<void*> m_freeBlocks; // 存储空闲块的列表
    size_t m_usedBlocks;           // 当前使用的块数
};


class Bullet {
public:
    Bullet(int x, int y) : m_x(x), m_y(y) {
        std::cout << "Bullet created at (" << x << ", " << y << ")\n";
    }
    ~Bullet() {
        std::cout << "Bullet destroyed\n";
    }
    // 其他Bullet方法...

private:
    int m_x, m_y; // 子弹位置
};

class BulletPool {
public:
    BulletPool(size_t size) : m_pool(sizeof(Bullet), size) {}

    Bullet* acquire(int x, int y) {
        void* mem = m_pool.allocate();
        if (!mem) return nullptr; // 如果没有可用的子弹,返回nullptr
        return new (mem) Bullet(x, y); // 使用placement new创建Bullet
    }

    void release(Bullet* bullet) {
        bullet->~Bullet(); // 显式调用析构函数
        m_pool.deallocate(bullet); // 将内存块返回到池中
    }

private:
    MemoryPool m_pool; // 内存池实例
};

// 示例使用
int main() {
    BulletPool bulletPool(5); // 创建一个可容纳5个子弹的池

    Bullet* bullet1 = bulletPool.acquire(10, 20);
    Bullet* bullet2 = bulletPool.acquire(15, 25);

    bulletPool.release(bullet1); // 释放子弹
    bulletPool.release(bullet2); // 释放子弹

    return 0;
}

4.2 高性能数据处理

在需要处理大量小数据结构时,内存池可以显著提高性能:

#include <iostream>
#include <vector>
#include <stdexcept>
#include <cassert>

class MemoryPool {
public:
    // 构造函数,初始化内存池
    MemoryPool(size_t blockSize, size_t blockCount)
        : m_blockSize(blockSize), m_blockCount(blockCount), m_usedBlocks(0) {
        // 分配内存池
        m_pool = malloc(blockSize * blockCount);
        if (!m_pool) {
            throw std::bad_alloc(); // 内存分配失败,抛出异常
        }
        // 初始化空闲块列表
        for (size_t i = 0; i < blockCount; ++i) {
            m_freeBlocks.push_back(static_cast<char*>(m_pool) + i * blockSize);
        }
    }

    // 析构函数,释放内存池
    ~MemoryPool() {
        free(m_pool);
    }

    // 分配内存块
    void* allocate() {
        // 如果没有可用的块,返回nullptr
        if (m_freeBlocks.empty()) {
            return nullptr; // 可以改进为扩展内存池
        }
        // 从空闲块列表中取出一个块
        void* block = m_freeBlocks.back();
        m_freeBlocks.pop_back(); // 从空闲列表中移除
        m_usedBlocks++; // 增加使用计数
        return block; // 返回分配的块
    }

    // 释放内存块
    void deallocate(void* block) {
        assert(block != nullptr); // 确保要释放的块不是nullptr
        m_freeBlocks.push_back(static_cast<char*>(block)); // 添加到空闲块列表
        m_usedBlocks--; // 减少使用计数
    }

    // 获取当前使用的块数
    size_t usedBlocks() const {
        return m_usedBlocks;
    }

    // 获取空闲块的数量
    size_t freeBlocks() const {
        return m_freeBlocks.size();
    }

private:
    size_t m_blockSize;            // 每个内存块的大小
    size_t m_blockCount;           // 内存池中的块数量
    void* m_pool;                  // 内存池的起始地址
    std::vector<void*> m_freeBlocks; // 存储空闲块的列表
    size_t m_usedBlocks;           // 当前使用的块数
};


class Data {
public:
    Data(int value) : m_value(value) {}
    ~Data() {}
    // 数据处理方法...

private:
    int m_value; // 数据值
};

class DataPool {
public:
    DataPool(size_t size) : m_pool(sizeof(Data), size) {}

    Data* create(int value) {
        void* mem = m_pool.allocate();
        return new (mem) Data(value); // 使用placement new创建数据对象
    }

    void destroy(Data* data) {
        data->~Data(); // 显式调用析构函数
        m_pool.deallocate(data); // 将内存块返回到池中
    }

private:
    MemoryPool m_pool; // 内存池实例
};

// 示例使用
int main() {
    DataPool dataPool(100); // 创建一个可容纳100个Data对象的池

    Data* data1 = dataPool.create(42);
    Data* data2 = dataPool.create(99);

    dataPool.destroy(data1); // 释放数据
    dataPool.destroy(data2); // 释放数据

    return 0;
}

7. 总结

内存池是一种有效的内存管理技术,通过预分配和集中管理内存块,提高了内存分配和释放的效率。尽管它增加了一定的复杂性,但在高性能和实时系统中,它的优势往往是不可忽视的。理解内存池的基本概念、设计思路和使用场景,有助于在适当的地方应用这一技术。

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

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

相关文章

谷歌收录批量查询,谷歌收录批量查询的简单方法

谷歌收录批量查询是网站管理员和SEO优化人员常见的需求&#xff0c;以下提供几种简单且高效的批量查询方法&#xff1a; 一、使用Google Search Console&#xff08;谷歌搜索控制台&#xff09; 注册并验证网站&#xff1a; 首先&#xff0c;确保你已经在Google Search Conso…

【JVM】垃圾释放方式:标记-清除、复制算法、标记-整理、分代回收

文章目录 1. 标记-清除2. 复制算法4. 标记-整理4. 分代回收 把标记为垃圾的对象的内存空间进行释放。主要有三种释放方式 1. 标记-清除 把标记为垃圾的对象&#xff0c;直接释放掉&#xff08;最朴素的做法&#xff09; 此时就是把标记为垃圾的对象所对应的内存空间直接释放。…

Visual Studio C# 处理和修复 WinRiver II 测量项目 MMT 文件错误

Visual Studio C# 处理和修复 WinRiver II 测量项目 MMT 文件错误 前言一、WinRiver II 测量项目 MMT 文件的结构二、WinRiver II 无法打开或操作测量项目 MMT 文件2.1 无法载入船测多线法测量文件2.2 可以载入测验项目 MMT 文件&#xff0c;但 ADCP 后处理软件无法写入信息2.3…

【数学分析笔记】第4章第4节 复合函数求导法则及其应用(2)

4. 微分 4.4 复合函数求导法则及其应用 【例4.4.3】 y e 1 cos ⁡ x ye^{\sqrt{1\cos x}} ye1cosx ​&#xff0c;求 y ′ y y′ 【解】 y ′ e 1 cos ⁡ x ⋅ 1 2 1 cos ⁡ x ⋅ ( − sin ⁡ x ) − sin ⁡ x 2 1 cos ⁡ x e 1 cos ⁡ x ye^{\sqrt{1\cos x}}\cdot\f…

JavaScript 中最快的循环是什么?

无论使用哪种编程语言&#xff0c;循环都是一种内置功能。JavaScript 也不例外&#xff0c;它提供了多种实现循环的方法&#xff0c;偶尔会给开发人员带来困惑&#xff1a;哪一种循环才是最快的&#xff1f; 以下是Javascript中可以实现循环的方法&#xff1a; For Loop While …

Pikachu- Over Permission-垂直越权

以admin 账号登陆&#xff0c;添加一个用户&#xff1b; 把添加用户的这个请求发送到 repeater&#xff1b; 退出admin&#xff0c;使用普通用户pikachu登陆&#xff1b; 只有查看权限&#xff1b; 使用pikachu 用户的认证信息&#xff0c;替换repeater处管理员创建用户请求的…

0基础学前端 day6 -- 搭建github pages静态网址

标题&#xff1a;如何通过 GitHub Pages 创建一个静态网站 GitHub Pages 是 GitHub 提供的一项免费服务&#xff0c;允许用户从 GitHub 仓库中托管静态网站。对于开发者和非开发者来说&#xff0c;这都是一个极其便利的工具&#xff0c;用于创建和发布个人博客、项目文档或作品…

Redis中GEO数据结构实现附近商户搜索

Redis的版本必须是6.2以上 在测试类中将数据导入Redis Testvoid loadShopData(){//1.查询店铺信息List<Shop> list shopService.list();//2.把店铺分组&#xff0c;按照typeId分组&#xff0c;typeId一致的放到一个集合Map<Long, List<Shop>> map list.s…

在vscode在使用idea编辑器的快捷键

在vscode在使用idea编辑器的快捷键 在vscode扩展在搜索idea key结果如下&#xff1a; 选择IntelliJ IDEA Keybindings安装&#xff08;注意作者是Keisuke Kato&#xff09;&#xff0c;安装后就可以在vscode编辑器中使用idea编辑器的快捷键。

五子棋双人对战项目(2)——登录模块

目录 一、数据库模块 1、创建数据库 2、使用MyBatis连接并操作数据库 编写后端数据库代码 二、约定前后端交互接口 三、后端代码编写 文件路径如下&#xff1a; UserAPI&#xff1a; UserMapper&#xff1a; 四、前端代码 登录页面 login.html&#xff1a; 注册页面…

ZenStack全栈开发工具(一)快速使用指南

简介 ZenStack是一个TypeScript工具&#xff0c;通过灵活的授权和自动生成的类型安全的 API/钩子来增强 Prisma ORM&#xff0c;从而简化全栈开发 数据库-》应用接口 数据库-》前端 参考官方网站&#xff1a;https://zenstack.dev/ 如果我们想做一个全栈开发的web应用程序&am…

记一次教学版内网渗透流程

信息收集 如果觉得文章写的不错可以共同交流 http://aertyxqdp1.target.yijinglab.com/dirsearch dirsearch -u "http://aertyxqdp1.target.yijinglab.com/"发现 http://aertyxqdp1.target.yijinglab.com/joomla/http://aertyxqdp1.target.yijinglab.com/phpMyA…

算法笔记(九)——栈

文章目录 删除字符串中的所有相邻重复项比较含退格的字符串基本计算机II字符串解码验证栈序列 栈是一种先进后出的数据结构&#xff0c;其操作主要有 进栈、压栈&#xff08;Push&#xff09; 出栈&#xff08;Pop&#xff09; 常见的使用栈的算法题 中缀转后缀逆波兰表达式求…

大学生就业市场:Spring Boot招聘系统的设计与实现

4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式&#xff0c;是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示&#xff1a; 图4-1系统工作原理…

MySQL中NULL值是否会影响索引的使用

MySQL中NULL值是否会影响索引的使用 为何写这一篇文章 &#x1f42d;&#x1f42d;在面试的时候被问到NULL值是否会走索引的时候&#xff0c;感到有点不理解&#xff0c;于是事后就有了这篇文章 问题&#xff1a; 为name建立索引&#xff0c;name可以为空select * from user …

OpenHarmony标准系统上实现对rk系列芯片NPU的支持(npu使用)

在上篇文章中&#xff0c;我们学习了移植rk的npu驱动到OpenHarmony提供的内核。本文我们来学习如何在OpenHarmony标准系统rk系列芯片如何使用npu OpenHarmony RK系列芯片运行npu测试用例 在移植npu驱动到OpenHarmony之后&#xff0c;来运行npu样例进行简单测试 1.O 测试准备…

ModuleNotFoundError: No module named ‘package‘

报错&#xff1a; Traceback (most recent call last): File “”, line 198, in run_module_as_main File “”, line 88, in run_code File "D:\python\helloworld.venv\Scripts\pip.exe_main.py", line 4, in File "D:\python\helloworld.venv\Lib\site-pac…

昇思学习打卡营第32天|基于ResNet50的中药炮制饮片质量判断模型

背景介绍 中药炮制是根据中医药理论&#xff0c;依照临床用药需求&#xff0c;通过调剂和制剂要求&#xff0c;将中药材制备成中药饮片的过程。老百姓日常使用的中药饮片&#xff0c;是中药炮制技术的成果。中药炮制过程中&#xff0c;尤其是涉及到水火处理时&#xff0c;必须注…

电器自动化入门08:隔离变压器、行程开关介绍及选型

视频链接&#xff1a;3.4 电工知识&#xff1a;三相交流异步电动机自动往返行程控制及控制变压器选型_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1PJ41117PW?p8&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5 1.隔离&#xff08;控制&#xff09;变压器 2.行程开…

【AI】AIOT简介

随着技术的快速发展&#xff0c;人工智能AI和物联网IoT已经成为当今最热门的技术领域。AIOT是人工智能和物联网的结合&#xff0c;使物联网设备更加智能化&#xff0c;能够进行自主决策和学习的技术。 通过物联网产生、收集来自不同维度的、海量的数据存储于云端、边缘端&#…