实现定长的内存池

池化技术

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

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

什么是定长池

        我们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需要处理各种各样的场景,根据用户需要分配不同大小的内存块,空闲空间的管理也要复杂得多。

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

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

相关文章

路由器原理与配置技术详解

一、路由基础原理 1.1 路由器的核心功能 网络层设备&#xff1a;工作在OSI参考模型第三层&#xff0c;实现不同网络间的互联互通智能路径选择&#xff1a;基于路由表为数据包选择最优传输路径协议转换&#xff1a;处理不同网络接口间的协议差异&#xff08;如以太网与PPP&…

Leetcode 3518. Smallest Palindromic Rearrangement II

Leetcode 3518. Smallest Palindromic Rearrangement II 1. 解题思路2. 代码实现 题目链接&#xff1a;Leetcode 3518. Smallest Palindromic Rearrangement II 1. 解题思路 这一题是题目Leetcode 3517. Smallest Palindromic Rearrangement I的升级版本&#xff0c;其主要的…

大模型——Crawl4AI 中的数据提取策略

大模型——Crawl4AI 中的数据提取策略 在本章中,将详细介绍在 Crawl4AI 中可用的数据提取策略。这些策略包括: LLMExtractionStrategy:用于详细内容提取。JsonCssExtractionStrategy:使用 CSS 选择器进行结构化数据检索。CosineStrategy:基于余弦相似性进行有效的语义分段…

职坐标解码互联网行业转型发展新动能

当前&#xff0c;互联网行业正以前所未有的速度重塑全球产业格局。工信部最新数据显示&#xff0c;我国互联网企业营收连续三年保持双位数增长&#xff0c;其中百强企业在人工智能、物联网等领域的投入强度同比提升40%&#xff0c;展现出强劲的技术引领力。与此同时&#xff0c…

linux多线(进)程编程——(4)进程间的传音术(命名管道)

前言&#xff08;前情回顾&#xff09; 进程君&#xff08;父进程&#xff09;在开发出匿名管道这门传音术后&#xff0c;解决了和自己孩子&#xff08;子进程&#xff09;间的沟通问题&#xff0c;父子关系趋于融洽。和孩子沟通后&#xff0c;进程君发现&#xff0c;自己脱离…

在IDEA里面建立maven项目(便于java web使用)

具体步骤&#xff1a; 第一次有的电脑你再创建项目的时候右下角会提醒你弹窗&#xff1a;让你下载没有的东西 一定要下载&#xff01;&#xff01;可能会很慢 运行结果&#xff1a; 因为他是默认的8080端口所以在运行的时候输入的url如下图&#xff1a; 新建了一个controller代…

【13】数据结构之树结构篇章

目录标题 树Tree树的定义树的基本概念树的存储结构双亲表示法孩子表示法孩子兄弟表示法 二叉树二叉树与度不超过&#xff12;的普通树的不同之处二叉树的基本形态二叉树的分类二叉树的性质 二叉树的顺序存储二叉树的链式存储二叉树的链式存储的结点结构树的遍历先序遍历中序遍历…

雷达生命探测仪,地震救援的生命探测先锋|鼎跃安全

在地震、山体滑坡、坍塌建筑等突发灾害中&#xff0c;会严重摧毁建筑物&#xff0c;造成倒塌和人员被困&#xff1b;在瓦砾堆、混凝土板层中&#xff0c;受困人员的生命安全常常面临严峻威胁。传统救援手段通常存在响应时间长、监测精度有限等不足。 救援现场往往环境复杂&…

512天,倔强生长:一位技术创作者的独白

亲爱的读者与同行者&#xff1a; 我是倔强的石头_&#xff0c;今天是我在CSDN成为创作者的第512天。当系统提示我写下这篇纪念日文章时&#xff0c;我恍惚间想起了2023年11月19日的那个夜晚——指尖敲下《开端——》的标题&#xff0c;忐忑又坚定地按下了“发布”键。那时的我…

数据结构*集合框架顺序表-ArrayList

集合框架 常见的集合框架 什么是顺序表 顺序表是一种线性表数据结构&#xff0c;它借助一组连续的存储单元来依次存储线性表中的数据元素。一般情况下采用数组存储。 在数组上完成数据的增删查改。 自定义简易版的顺序表 代码展示&#xff1a; public interface IArray…

使用openpyxl时的一些注意点

一、是否需要close()&#xff1f; 在使用 openpyxl 时&#xff0c;wb.save() 后一般不需要再手动调用 wb.close()。wb.save() 会自动处理文件写入和释放。 如果是使用openpyxl.load_workbook(filename, read_onlyTrue) 打开了一个只读模式的工作簿&#xff0c;此时会建立文件…

Python爬虫第11节-解析库Beautiful Soup的使用上篇

目录 前言 一、Beautiful Soup 简介 1.1 Beautiful Soup概述 1.2 准备工作 1.3 解析器 二、基本使用 三、节点选择器的使用 3.1 选择元素 3.2 提取信息 3.2.1 获取名称 3.2.2 获取属性 3.2.3 获取内容 3.3 嵌套选择 3.4 关联选择 3.4.1 子节点和子孙节点 3.4.2…

【Docker-13】Docker Container容器

Docker Container&#xff08;容器&#xff09; 一、什么是容器&#xff1f; 通俗地讲&#xff0c;容器是镜像的运行实体。镜像是静态的只读文件&#xff0c;而容器带有运行时需要的可写文件层&#xff0c;并且容器中的进程属于运行状态。即容器运行着真正的应用进程。容器有…

Spring Cache(笔记)

简介&#xff1a; 常用注解&#xff1a;

大模型Qwen32b(FP16精度)部署所需的显存大小和并发数计算分析

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下大模型Qwen32b(FP16精度)部署所需的显存大小和并发计算分析。 文章目录 1. 大模型显存需求分析1.1 模型参数与显存占用1.2 不同精度对显存的影响 2. 不同显卡配置下的并发能力2.1 80G显卡并发能力2.2 64G显卡并发能…

【euclid】10.2 2D变换模块(transform2d.rs)Arbitrary trait

源码 #[cfg(feature "arbitrary")] impl<a, T, Src, Dst> arbitrary::Arbitrary<a> for Transform2D<T, Src, Dst> whereT: arbitrary::Arbitrary<a>, {fn arbitrary(u: &mut arbitrary::Unstructured<a>) -> arbitrary::Res…

MAC Mini M4 上测试Detectron2 图像识别库

断断续续地做图像识别的应用&#xff0c;使用过各种图像识别算法&#xff0c;一开始使用openCV 做教室学生计数的程序。以后又使用YOLO 做医学伤口检测程序。最近&#xff0c;开始使用meta 公司的Detectron2.打算做OCR 文档结构分析 Detectron2 的开发者是 Meta 的 Facebook AI…

一天时间,我用AI(deepseek)做了一个配色网站

前言 最近在开发颜色搭配主题的相关H5和小程序&#xff0c;想到需要补充一个web网站&#xff0c;因此有了这篇文章。 一、确定需求 向AI要答案之前&#xff0c;一定要清楚自己想要做什么。如果你没有100%了解自己的需求&#xff0c;可以先让AI帮你理清逻辑和思路&#xff0c;…

机器视觉用消色差双合透镜

光学系统案例&#xff1a;机器视觉用消色差双合透镜 一、设计规格 1. 应用场景&#xff1a;专为工业相机成像而设计&#xff0c;工作于可见光波段&#xff0c;旨在满足该领域对高精度成像的需求。 2. 核心参数&#xff1a; • 焦距&#xff1a;精确要求达到 50 mm 1%&#…

批量归一化(Batch Normalization)原理与PyTorch实现

批量归一化&#xff08;Batch Normalization&#xff09;是加速深度神经网络训练的常用技术。本文通过Fashion-MNIST数据集&#xff0c;演示如何从零实现批量归一化&#xff0c;并对比PyTorch内置API的简洁实现方式。 1. 从零实现批量归一化 1.1 批量归一化函数实现 import t…