当前位置: 首页 > article >正文

实现定长的内存池

池化技术

        所谓的池化技术,就是程序预先向系统申请过量的资源,然后自己管理起来,以备不时之需。这个操作的价值就是,如果申请与释放资源的开销较大,提前申请资源并在使用后并不释放而是重复利用,能够提高程序运行效率和减少开销。

        在计算机领域,池化技术有非常多的应用场景,如内存池、连接池、线程池和对象池等。以服务器中的线程池为例,它的主要思想是:预先启动一批线程,让它们先进入睡眠状态,当有客户端请求到来时,唤醒一个线程进行处理,并在处理完请求后,继续睡眠,等待下一次被唤醒。

什么是定长池

        我们C语言中使用的malloc实际上是标准库的函数,底层实现实际上就使用了内存池技术,它支持根据我们的需求分配不同大小的内存空间,而我们今天要设计的定长池则每次只能分配固定大小的空间,在频繁申请大小相同的空间的情况下,效率比malloc更优秀。

系统调用

        在windows环境下进行开发,所以使用windows内存申请的API:

VirtualAlloc

在进程的虚拟地址空间中分配或保留内存

#include <windows.h>LPVOID VirtualAlloc
(LPVOID IpAddress,      // 要分配的内存区域的地址SIZE_T dwSize,         // 分配的大小DWORD  flAllocationType,// 分配的类型DWORD  flProtect       // 该内存的初始保护属性
);

参数解释:

lpAddress:指定要分配的内存区域的起始地址。如果此参数为nullptr,则系统会自动决定分配内存区域的位置,并且按64KB向上取整。

dwSize:指定要分配或保留的区域的大小,以字节为单位。系统会根据这个大小一直分配到下页的边界。

flAllocationType:指定分配类型,可以是指定或合并以下标志:

  • MEM_COMMIT:为指定地址空间提交物理内存。
  • MEM_RESERVE:保留指定地址空间,不分配物理内存。这样可以阻止其他内存分配函数(如malloc和LocalAlloc等)再使用已保留的内存范围,直到它被释放。
  • MEM_TOP_DOWN:在尽可能高的地址分配内存。
  • MEM_LARGE_PAGES:分配内存时使用大页面支持。大小和对齐必须是一个大页面的最低倍数。

flProtect:指定被分配区域的访问保护方式。可能的值包括:

  • PAGE_READWRITE:区域可以执行代码,应用程序可以读写该区域。
  • PAGE_READONLY:区域为只读。如果应用程序试图访问区域中的页,将会被拒绝访问。
  • PAGE_NOACCESS:任何访问该区域的操作将被拒绝。
  • PAGE_GUARD:区域第一次被访问时进入一个STATUS_GUARD_PAGE异常,这个标志要和其他保护标志合并使用。
  • PAGE_NOCACHE:RAM中的页映射到该区域时将不会被微处理器缓存(cached)。

返回值:

如果函数调用成功,则返回分配的首地址;
如果调用失败,则返回nullptr。可以通过GetLastError函数来获取错误信息。

// 直接去堆上按页申请空间
inline static void* SystemAlloc(size_t kpage)
{
#ifdef _WIN32// 8K一页为单位向操作系统申请内存空间void* ptr = VirtualAlloc(0, kpage << 13, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else// linux下brk mmap等
#endifif (ptr == nullptr)throw std::bad_alloc();return ptr;
}static void*& NextObj(void* obj)
{return *(void**)obj;
}

定长池设计

一个分配固定大小内存的模板类,由于申请的内存大小固定,所以申请固定大小的空间时,性能比malloc更好一些,目前暂时不考虑内存碎片问题。

管理的成员:

  • 预先申请的内存空间的指针memory

  • 管理用户释放空间的空闲链表的指针freelist

    • 空闲链表连接的方式是:用户将内存释放后,该内存空间的前4/8字节(取决于系统位数)空间用于存放下一块内存空间的地址。

    • 插入新空间到空闲链表:通过头插法实现,由于系统位数不确定,所以使用二级指针解引用来获得指针的大小,从而可以适用于32/64位系统。

  • 记录空间剩余大小的字段remain_size

template<class T>
class ObjectPool
{
private:char* _memory = nullptr; // 预先申请的内存空间size_t _remain_size = 0; // 剩余空间大小void* _freelist = nullptr; // 管理用户释放空间的空闲链表
};

提供的方法:

  • New:用户申请空间的接口。

    • 如果剩余空间大小不足一个空间,则重新开辟一块新的固定大小的内存空间。

    • 使用定位new,显示调用构造函数后返回对象指针给用户

    • 更新memory指针偏移量和remain_size大小,如果T类型大小不足以存放下一块空间的地址,则更新大小应为指针的大小。

T* New()
{T* obj = nullptr;// 优先使用空闲链表中的空间if (_freelist != nullptr){void* next = *((void**)_freelist);obj = (T*)_freelist;_freelist = next;}else{// 当空间不足时,开辟固定大小的空间if (_remain_size < sizeof T){_remain_size = 128 * 1024;//_memory = (char*)malloc(128 * 1024); // 定长池,开辟128KB的内存空间_memory = (char*)SystemAlloc(128 * 1024); // 定长池,开辟128KB的内存空间if (_memory == nullptr){throw std::bad_alloc();}}obj = (T*)_memory;// 分配的空间的大小至少要能够存放下一块空间的地址size_t objSize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);_memory += objSize; // 分配定长空间后,指针向后偏移_remain_size -= objSize; // 更新剩余空间大小}// 定位new,显式调用T的构造进行初始化new(obj)T;return obj;
}
  • Delete,释放T*类型的指针指向的空间,但不会返回给操作系统,而是通过空闲链表管理起来。

    • 显式调用析构函数清理指针指向对象的资源,并将空闲空间头插到空闲链表。

    • 空闲链表连接的方式是:用户将内存释放后,该内存空间的前4/8字节(取决于系统位数)空间用于存放下一块内存空间的地址。

    • 插入新空间到空闲链表:通过头插法实现,由于系统位数不确定,所以使用二级指针解引用来获得指针的大小,从而可以适用于32/64位系统。

// 将用户要释放的空间用空闲链表管理起来
// 空闲链表连接的方式是:空间的前4/8字节(其实就是指针的大小,具体取决于系统位数)存放下一块空间的地址
void Delete(T* obj)
{obj->~T();// 使用二级指针获取指针,头插法将空间添加到空闲链表*(void**)obj = _freelist;_freelist = obj;
}

性能测试

接下来我们对定长池进行性能测试,并与malloc进行比较,以下是测试代码:

void TestPool()
{const int round = 5;const int times = 50000;std::vector<TreeNode*> v1;v1.reserve(5);size_t begin1 = clock();for (size_t j = 0; j < round; ++j){for (int i = 0; i < times; ++i){v1.push_back(new TreeNode);}for (int i = 0; i < times; ++i){delete v1[i];}v1.clear();}size_t end1 = clock();ObjectPool<TreeNode> TNPool;std::vector<TreeNode*> v2;v2.reserve(50000);size_t begin2 = clock();for (int i = 0; i < round; i++){for (int j = 0; j < times; j++){v2.push_back(TNPool.New());}for (int j = 0; j < times; j++){TNPool.Delete(v2[i]);}v2.clear();}size_t end2 = clock();std::cout << "malloc耗时:" << end1 - begin1 << std::endl;std::cout << "ObjectPool耗时:" << end2 - begin2 << std::endl;
}

Debug版本的比较:

Release版本的比较:

可以发现,在高频分配固定大小对象的场景下,定长池的效率要比malloc更高,这是因为:

定长池只用于分配固定大小的对象,每次开辟的都是固定大小的内存块,管理空闲空间也只需要使用简单的空闲链表就能完成;而malloc需要处理各种各样的场景,根据用户需要分配不同大小的内存块,空闲空间的管理也要复杂得多。

相关文章:

实现定长的内存池

池化技术 所谓的池化技术&#xff0c;就是程序预先向系统申请过量的资源&#xff0c;然后自己管理起来&#xff0c;以备不时之需。这个操作的价值就是&#xff0c;如果申请与释放资源的开销较大&#xff0c;提前申请资源并在使用后并不释放而是重复利用&#xff0c;能够提高程序…...

vs2022使用git方法

1、创建git 2、在cmd下执行 git push -f origin master &#xff0c;会把本地代码全部推送到远程&#xff0c;同时会覆盖远程代码。 3、需要设置【Git全局设置】&#xff0c;修改的代码才会显示可以提交&#xff0c;否则是灰色的不能提交。 4、创建的分支&#xff0c;只要点击…...

Mysql中表的使用(3)

目录 1.updata的使用 2.delete(删除表中数据)drop&#xff08;删除表&#xff09; 数据库的约束 1.NOT NULL 指定列不能为空 2.UNIQUE指定列唯一 3.DEFAULT(默认值) 4.PRIMARY KEY 5.自增主键 1.updata的使用 1.0update 表名 set 列名x where 列名y; 2.0update 表名 s…...

BUUCTF-Web(1-20)

目录 一.SQL注入 (1)[极客大挑战 2019]EasySQL 万能密码 (7)[SUCTF 2019]EasySQL 堆叠注入 解一&#xff1a; 解二&#xff1a; (10)[强网杯 2019]随便注 堆叠注入 解一&#xff1a; 解二&#xff1a; 解三&#xff1a; (8)[极客大挑战 2019]LoveSQL 联…...

Uniapp:确认框

目录 一、 出现场景二、 效果展示三、具体使用 一、 出现场景 在项目的开发中&#xff0c;会经常出现删除数据的情况&#xff0c;如果直接删除的话&#xff0c;可能会存在误删&#xff0c;用户体验不好&#xff0c;所以需要增加一个消息提示&#xff0c;提醒用户是否删除。 二…...

【前端网络请求入门】XMLHttpRequest与Fetch保姆级教程

新手学前端时&#xff0c;经常会被「如何让网页和服务器说话」难住。今天我们用最通俗的语言&#xff0c;把浏览器最常用的两种网络请求方式——XMLHttpRequest和Fetch讲清楚&#xff0c;还会带完整的代码示例&#xff0c;跟着敲一遍就能上手&#xff01; 一、先搞懂「网络请求…...

力扣热题100—滑动窗口(c++)

3.无重复字符的最长子串 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长 子串 的长度。 unordered_set<char> charSet; // 用于保存当前窗口的字符int left 0; // 窗口左指针int maxLength 0; // 最长子串的长度for (int right 0; right < s.siz…...

实验四 中断实验

一、实验目的 掌握中断服务程序的编写。 二、实验电路 三、实验内容 1&#xff0e;实验用PC机内部的中断控制器8259A&#xff0c;中断源用TPC-ZK实验箱上的单脉冲电路&#xff0c;将单脉冲电路的输出接中断请求信号IRQ&#xff0c;每按一次单脉冲按键产生一次…...

腾势品牌欧洲市场冲锋,科技豪华席卷米兰

在时尚与艺术的交汇点&#xff0c;米兰设计周的舞台上&#xff0c;一场汽车界的超级风暴正在酝酿&#xff0c;腾势品牌如一头勇猛无畏的雄狮&#xff0c;以雷霆万钧之势正式向欧洲市场发起了冲锋。其最新力作——腾势Z9GT的登场&#xff0c;仿佛是一道闪电划破夜空&#xff0c;…...

第五阶段:项目实践与后续学习指引

模块 10&#xff1a;综合项目实践 在这个模块中&#xff0c;我们将通过实际项目来综合运用前面所学的 Python 知识。我们会选择一个命令行记事本应用作为主要示例&#xff0c;同时简要介绍其他项目的思路。 项目&#xff1a;命令行记事本应用 1. 项目规划 良好的项目规划是…...

MoogDB数据库日常维护技巧与常见问题解析

在当今的数据驱动世界中&#xff0c;数据库作为信息存储与管理的核心组件&#xff0c;扮演着举足轻重的角色。MoogDB作为一款高性能、易扩展的数据库解决方案&#xff0c;越来越受到开发者和企业的青睐。为了确保MoogDB的稳定性与高性能&#xff0c;定期的日常维护及对常见问题…...

Java 中的各种锁详解

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/literature?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;…...

多店铺商城_多商户商城系统源码_免费开源!

在电商行业快速发展的今天&#xff0c;多店铺商城系统&#xff08;B2B2C模式&#xff09;已成为企业实现平台化运营的核心工具&#xff0c;就像我们平时用的淘宝&#xff0c;京东那样。如果你想做一个电商平台就需要这种多店铺商城系统。 本文将深入探讨多商户商城系统的核心功…...

【2025年泰迪杯数据挖掘挑战赛】A题 数据分析+问题建模与求解+Python代码直接分享

目录 2025年泰迪杯数据挖掘挑战赛A题完整论文&#xff1a;建模与求解Python代码1问题一的思路与求解1.1 问题一的思路1.1.1对统计数据进行必要说明&#xff1a;1.1.2统计流程&#xff1a;1.1.3特殊情况的考虑&#xff1a; 1.2 问题一的求解1.2.1代码实现1.2.2 问题一结果代码分…...

NO.95十六届蓝桥杯备战|图论基础-单源最短路|负环|BF判断负环|SPFA判断负环|邮递员送信|采购特价产品|拉近距离|最短路计数(C++)

P3385 【模板】负环 - 洛谷 如果图中存在负环&#xff0c;那么有可能不存在最短路。 BF算法判断负环 执⾏n轮松弛操作&#xff0c;如果第n轮还存在松弛操作&#xff0c;那么就有负环。 #include <bits/stdc.h> using namespace std;const int N 2e3 10, M 3e3 1…...

ROS2模块库概览

一、核心通信与基础库&#xff08;最常用&#xff09; 客户端库 rclcpp (ROS Client Library for C) 核心API&#xff1a;create_node(), create_publisher(), create_subscription()高级特性&#xff1a; 生命周期节点&#xff1a;通过rclcpp_lifecycle实现configure/activate…...

在机器视觉检测中为何选择线阵工业相机?

线阵工业相机&#xff0c;顾名思义是成像传感器呈“线”状的。虽然也是二维图像&#xff0c;但极宽&#xff0c;几千个像素的宽度&#xff0c;而高度却只有几个像素的而已。一般在两种情况下使用这种相机&#xff1a; 1. 被测视野为细长的带状&#xff0c;多用于滚筒上检测的问…...

Linux应急中常用命令

pwdx {pid} 提供进程的 当前工作目录&#xff0c;即进程正在操作的目录。它显示的是进程的运行时工作目录&#xff0c;而不是启动时的可执行文件路径。等同于ls -al /proc//cwdps -aux 和 top 都是用来查看 Linux 系统中进程的命令&#xff0c;但它们的功能、输出格式和使用场景…...

Node.js 文件读取与复制相关内容

Node.js 文件读取与复制相关内容的系统总结&#xff0c;包括 同步读取、异步读取、流式读取、复制操作、两者对比及内存测试。 &#x1f9e9; 一、Node.js 文件读取方式总结 Node.js 使用 fs&#xff08;文件系统&#xff09;模块进行文件操作&#xff1a; 1. 同步读取&#…...

数据结构-串

串是数据结构中一种重要的数据类型&#xff0c;广泛应用于文本处理、信息检索等领域。本文将从串的基本概念、存储实现、应用举例以及总结核心知识点四个方面进行详细讲解&#xff0c;帮助大家更好地理解和掌握串这一数据结构。 一、串的基本概念及其抽象数据类型 1.1 串的定…...

Windows 下 MongoDB ZIP 版本安装指南

在开发和生产环境中&#xff0c;MongoDB 是一种非常流行的 NoSQL 数据库&#xff0c;以其灵活性和高性能而受到开发者的青睐。对于 Windows 用户来说&#xff0c;MongoDB 提供了多种安装方式&#xff0c;其中 ZIP 版本因其灵活性和轻量级的特点&#xff0c;成为很多开发者的首选…...

在 Spring Boot 中实现服务器端推送(SSE):两种方法的比较与实践

在现代 Web 应用中&#xff0c;实时数据推送是一个常见的需求。无论是实时消息通知、股票行情更新&#xff0c;还是在线游戏的实时数据交互&#xff0c;服务器端推送&#xff08;Server-Sent Events&#xff0c;简称 SSE&#xff09;都是一种高效且易于实现的解决方案。在 Spri…...

2025年十六届蓝桥杯Python B组原题及代码解析

相关试题可以在洛谷上测试用例&#xff1a; 2025 十六届 蓝桥杯 Python B组 试题 A&#xff1a;攻击次数 答案&#xff1a;103 print(103)代码&#xff1a; # 初始化敌人的血量 x 2025# 初始化回合数 turn 0# 模拟攻击过程 while x > 0:# 回合数加一turn 1# 第一个英…...

数据清洗到底在清洗什么?

在大数据时代&#xff0c;数据是每个企业的五星资产&#xff0c;被誉为“新石油”&#xff0c;但未经处理的数据往往参杂着大量“杂质”。这些“脏数据”不仅影响分析结果&#xff0c;严重的甚至误导企业决策。数据清洗作为数据预处理的关键环节&#xff0c;正是通过“去芜存菁…...

Microsoft Azure 基础知识简介

Microsoft Azure 基础知识简介 已完成100 XP 2 分钟 Microsoft Azure 是一个云计算平台&#xff0c;提供一系列不断扩展的服务&#xff0c;可帮助你构建解决方案来满足业务目标。 Azure 服务支持从简单到复杂的一切内容。 Azure 具有简单的 Web 服务&#xff0c;用于在云中托…...

mysql表类型查询

普通表 SELECT table_schema AS database_name,table_name FROM information_schema.tables WHERE table_schema NOT IN (information_schema, mysql, performance_schema, sys)AND table_type BASE TABLEAND table_name NOT IN (SELECT DISTINCT table_name FROM informatio…...

数据库ALGORITHM = INSTANT研究过程

背景 偶然在团队中发现同事大量使用 ALGORITHM INSTANT 更新字段&#xff0c;根据固有的理解&#xff0c;平时字段的更新必然会涉及到表结构的更改&#xff0c;印象中数据库会加入MDL锁去保证表数据的一致性。 但是听说在Mysql8.0特性中&#xff0c;表明在更新字段的时候此方法…...

n8n 为技术团队打造的安全工作流自动化平台

AI MCP 系列 AgentGPT-01-入门介绍 Browser-use 是连接你的AI代理与浏览器的最简单方式 AI MCP(大模型上下文)-01-入门介绍 AI MCP(大模型上下文)-02-awesome-mcp-servers 精选的 MCP 服务器 AI MCP(大模型上下文)-03-open webui 介绍 是一个可扩展、功能丰富且用户友好的…...

基于Python的App流量大数据分析与可视化方案

一、引言 App流量数据通常包括用户的访问时间、停留时间、点击行为、页面跳转路径等信息。这些数据分散在不同的服务器日志、数据库或第三方数据平台中&#xff0c;需要通过有效的技术手段进行整合和分析。Python在数据科学领域的广泛应用&#xff0c;得益于其简洁的语法、强大…...

【Linux 并发与竞争实验】

【Linux 并发与竞争实验】 之前学习了四种常用的处理并发和竞争的机制&#xff1a;原子操作、自旋锁、信号量和互斥体。本章我们就通过四个实验来学习如何在驱动中使用这四种机制。 文章目录 【Linux 并发与竞争实验】1.原子操作实验1.1 实验程序编写1.2 运行测试 2.自旋锁实验…...