【手写数据库内核组件】0301 动态内存池,频繁malloc/free让系统不堪重负,动态内存池让应用自由使用动态内存

动态内存管理

专栏内容

  • postgresql使用入门基础
  • 手写数据库toadb
  • 并发编程

个人主页:我的主页
管理社区:开源数据库
座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物.

文章目录

  • 动态内存管理
  • 一、概述
  • 二、动态内存管理原理
    • 2.1 内存分配策略
    • 2.2 内存回收策略
  • 三、动态内存管理实现
    • 3.1 内存管理结构定义
    • 3.2 memPage 申请与释放
    • 3.3 memNode 释放与释放
    • 3.4 应用申请内存
  • 四、总结
  • 结尾

一、概述


在平时运行时分配内存时,使用动态内存分配malloc/free或 new/delete,会直接从操作系统申请虚拟内存,释放时也归还给操作系统;

而操作系统会记录那些内存片段正在使用,那些已经释放;时间久后,内存碎片就越来越多,最终没法分配一片较大的内存出来,所以系统会尽量让空闲的片段连成一整片,不断的整理。

一般应用程序需要动态分配的次数和频率不高时,对操作系统的负担不重;

而像数据库这样的重型应用跑起来,会有大量的运行时动态内存的分配和释放,如果不对动态内存进行用户态的管理,必然会拖慢操作系统的运行,影响所有应用的性能。

本文就来分享一种在应用程序中进行动态内存管理的方法,减少向操作系统申请和释放的次数,来避免内存碎片整理的负担。

二、动态内存管理原理


动态内存管理的目标主要有两个:

  • 减少向操作系统申请和释放的次数,也就是减少调用malloc/free;
  • 减少内存碎片,一旦产生碎片就需整理,同时会造成一部分内存的浪费;

2.1 内存分配策略

针对这两个目标,我们可以这样来做,先从内存的分配来看:

  1. 每次从操作系统申请一块较大的内存;单次数量多,次数就会少很多;
  2. 应用需要时,从这块大内存中切割划分,直到大块内存用尽,再重复1步骤;
  3. 对于少量一次需要很大的内存时,超过每次申请的上限,此时就直接从操作系统申请,这就按特例来处理。

这有点像买蛋糕,当一个人吃时,每次就买一小块;当过生日时,有很多人一起吃,那就一次性买个大蛋糕,回来切分。

我们也可以说算法来源于生活,而高于生活!

2.2 内存回收策略

对于内存的释放回收,有两种策略:

  • 重复利用策略,释放时不归还给操作系统;下次申请时再重复利用;
  • 整体归还策略, 释放时不归还操作系统,如果一大块都释放时,则将此整块内存统一归还给操作系统;

重复利用策略

这种策略对于内存利用次数较高,与操作系统交互更少,但需要增加内存碎片的管理,也就是对于应用使用的不同大小的内存片,归还后要么进行移动合并,要么按大小进行排序再利用;

会增加碎片管理的负担,同时内存会有一些浪费。

整体归还策略

申请一大块内存,在应用内部进行切分为更小的片进行使用,当此大块内存上的分片均释放时,将此大块内存归还给操作系统。

这样避免内存碎片的整理,同时大块的释放也会减少操作系统内存碎片整理的负担;

但是也会存在内存浪费,当大块内存上有一小片一直不释放时,整块内存就会被浪费,迟迟得不到释放。

当然两种策略还可以再优化,但过多的优化,都会带来一些额外的开销。

三、动态内存管理实现


动态内存管理的基本单位称为内存页(page),大小为4KB,也就是上节提到的大块内存,每次申请与释放都会按内存页来操作。

在这里插入图片描述

  • 而对于应用程序来讲,它申请内存是从memPage中进行分配,每次分配一个memNode,包含申请的内存大小。

  • 当一个MemPage用完或不够时,再从下一个memPage中分配。

  • 所有的memPage采用单链表的形式串起来,方便释放时管理。

下面我们就分拆来了解一下,内存页的定义,以及内存的申请与释放的操作。

3.1 内存管理结构定义

内存管理需要记录mempage的链表,同时为了更少的与操作系统交互,增加了一个freeList来记录释放的memPage,当freeList不空时,直接从这里分配即可。

内存管理结构的定义

#define MEMORY_POOL_MANAGER_VERSION (0x0B10)
typedef struct MemPoolManagerContext
{int             version;unsigned long   totalSize;    /* 已经使用的动态内存大小 */// SPINLOCK        lock;         /* protected this structure. */DList           memFreeList;   DList           memPageList;      /* memPageInfo list */MemPageInfo     *currUsePage;
}MemPoolManagerContext;

成员说明:

  • version,模块的版本;
  • totalSize,记录当前使用了多少真实的内存空间;
  • memFreeList,空闲内存页的链表;
  • memPageList,正在使用的内存页的链表;
  • currUsePage,当前有最后一个内存页,下次分配时从此内存页上分配;

内存管页结构定义

内存页memPage作为向操作系统申请和释放的基本单位,它的定义如下:

#define MEMORY_POOL_PAGE_SIZE (4096)#define MEMPAGE_INFO_LEN (sizeof(MemPageInfo))
typedef struct MemPageInfo
{DList list;int memPageSize;int useOffset;int freeSize;  int releaseSize;         
}MemPageInfo;typedef char *MemPage, *MemPtr;

成员说明:

  • list,由双向链表来记录所有的内存页;
  • memPageSize,当前内存页的大小;可能有超大的内存页;
  • useOffset,使用内存的偏移;
  • freeSize,空闲内存大小;
  • releaseSize, 释放内存大小;记录当前内存页上使用过后,释放的内存大小,当全部释放后,此内存页也会释放;

这里新定义了一定类型MemPage, 也就是char *的别名,这样在后面分配内存页时使用,方便区分。

内存管节点结构定义

应用程序每次申请内存,都会分配一个内存节点结构,包含了申请的内存大小。

/* * MemAlloc will alloc a MemBlock, and return MemBlock->ptr for user.*/
typedef struct MemBlock 
{int size;                   /* memblock size + ptr[] size */MemPageInfo *memPage;       /* current memPageInfo pointer */char ptr[];
}MemBlock;

成员说明:

  • size , 记录当前内存节点的大小;
  • memPage,记录当前内存节点所属的内存块,指向内存页结构;
  • ptr, 返回给应用程序的内存首地址,这里没有定义大小;在低版本编译器中可能不支持,可以指定数组大小为1;

注意,这里ptr必须定义为数组,因为它就是数据的首地址,也就是一段内存的开始;如果定义为指针,需要手动赋值;

3.2 memPage 申请与释放

内存页的分配

当MemPage不够分配时,都需要从操作系统中申请一块新的memPage。

先从FreeList中查找,如果找到,就添加到当前使用列表中;

如果没有空闲memPage时,直接调用malloc进行分配。

static int AddNewPageToContext(MemPoolManagerContext *poolContext)
{MemPageInfo *currentMemPage = NULL;MemPage newPage = NULL;newPage = GetFreeMemPage();if(NULL == newPage){newPage = (MemPage)malloc(MEMORY_POOL_PAGE_SIZE);if(NULL == newPage){exit(-1);}}        currentMemPage = InitMemPage(newPage, MEMORY_POOL_PAGE_SIZE);/* add to context, and continue to alloc from context. */AddMemPageToContext(currentMemPage, poolContext);      return 0;
}

添加到使用列表中,同时将使用中的内存总数进行累加。

static int AddMemPageToContext(MemPageInfo *memPage, MemPoolManagerContext *context)
{context->totalSize += memPage->memPageSize;       /* add to mempage list */AddMemPageNode(&(context->memPageList), memPage);   context->currUsePage = memPage;return 0;
}

其中AddMemPageNode是将新的内存页加到链表中,这个在链表章节介绍。

3.3 memNode 释放与释放

每次应用申请内存,都是从内存池中分配一个memNode。

当申请的size 小于当前内存页的空闲空间时,在当前页中分配一个memBlock结构;

static MemPtr AllocFromMemPage(MemPageInfo *mPage, int size)
{MemBlock *memb = NULL;mPage->freeSize -= size;memb = (MemBlock*)((char *)mPage + mPage->useOffset);mPage->useOffset += size;memb->memPage = mPage;memb->size = size;return (MemPtr)(memb->ptr);
}

而当应用程序释放内存时,会将memBlock归还给对应的memPage;

在分配时,需要记录对应的memPage的地址,此时就可以进行引用。

static int ReleaseToMemPage(MemBlock *memb)
{MemPageListInfo *memPageList = NULL;MemPageInfo *memPage = memb->memPage;DList *header = NULL;int ret = 0;if(NULL == memPage){return -1;}memPage->releaseSize += memb->size;return 0;
}

释放时只是将待释放的内存大小增加到了 releaseSize;

当然这里可以将再进行优化,当memPage为空时,将它从使用列表中移除,并填加到空闲列表中。

3.4 应用申请内存

应用程序申请内存时,不能再使用malloc/free这两个接口了,需要使用自定义的接口。

在这里插入图片描述

动态内存申请接口

申请的流程如下:

  • 需要先定义一个全局的MemPoolManagerContext结构,来保存整个申请的内存页列表;通过GetMemPoolCurrentContext,可以达到单例获取的目的。
  • 申请的内存size需要增加MemBlock的头部成员的大小;
  • 当申请的size大于内存页可分配的上限时,直接分配一个非标准的内存页,将将它加到内存管理链表中;
  • 然后从内存管理中进行分配内存节点;
#define MEMORY_POOL_BLOCK_HEADER_SIZE (sizeof(MemBlock))
#define MEMPAGE_INFO_LEN (sizeof(MemPageInfo))
#define MEMORY_POOL_MAX_ALLOC_SIZE (MEMORY_POOL_PAGE_SIZE - MEMPAGE_INFO_LEN)MemPtr AllocFromMemPool(unsigned int size)
{MemPtr mem = NULL;MemPoolManagerContext *currentMemContext = NULL;MemPageInfo *currentMemPage = NULL;MemPage newPage = NULL;currentMemContext = GetMemPoolCurrentContext();if(NULL == currentMemContext){return NULL;}/* add memblock header size */size += MEMORY_POOL_BLOCK_HEADER_SIZE;/* oversize */if(size >= MEMORY_POOL_MAX_ALLOC_SIZE){newPage = (MemPage)malloc(size);currentMemPage = InitMemPage(newPage, size);AddMemPageToContext(currentMemPage, currentMemContext); mem = AllocFromMemPage(currentMemPage, size);return mem;}do {mem = AllocFromMemContext(size, currentMemContext);if(NULL != mem)break;AddNewPageToContext(currentMemContext);     }while(1);return mem;
}

这里有一个小技巧,后面使用了一个do { }while(1)的循环,其实这里就是为了写法简单,当没有空闲空间时,会申请一个内存页,然后继续分配内存节点。

动态内存释放接口

这里相对简单,就是将当前内存节点释放到对应的内存页上。


#define GetOffsetSize(type, member) (unsigned long)(&(((type *)(0))->member))
#define GetAddrByMember(memberaddr, member, type) (type *)(((char*)(memberaddr)) - GetOffsetSize(type,member))
#define GetMemBlockHeader(memptr) (GetAddrByMember(memptr, ptr, MemBlock))
int ReleaseToMemPool(MemPtr mem)
{return ReleaseToMemPage(GetMemBlockHeader(mem));
}

在应用程序中拿到的是ptr数组首地址,通过它在结构体中的偏移,可以找到memBlock结构的地址,这里定义了一个宏。

当然可以使用offsetof,这个预定义的函数来获取结构体成员的偏移。

四、总结


在动态内存使用频繁的应用程序中,不仅与操作系统交互多,而且会造成大量的内存碎片,增加额外的系统负担。

本文分享了通过动态内存池的方法,每次申请一个内存页,然后在当有动态内存需要时,进行切分,可以避够内存碎片的产生。

当然也存在很多可优化的地方,在释放时可以保留一部分内存页在freeList中,这样进一步减少与操作系统的交互。

结尾


非常感谢大家的支持,在浏览的同时别忘了留下您宝贵的评论,如果觉得值得鼓励,请点赞,收藏,我会更加努力!

作者邮箱:study@senllang.onaliyun.com
如有错误或者疏漏欢迎指出,互相学习。

注:未经同意,不得转载!

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

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

相关文章

RSA算法详解:万字文章详解RSA的加密与解密

本文目录 文章前言一、RSA的诞生1、加密算法的前世今生① 《六韬龙韬》中的阴符与阴书② 古罗马:凯撒密码③ 斯巴达:塞塔式密码(Scytale) 2、对称加密的脆弱性3、非对称加密算法的出现 二、RSA中的数学概念与定理1、质数理论2、关…

使用Qt和mitmproxy开发一个抓取网页短视频的万能工具

目录 实现原理 mitmproxy介绍 功能简介 安装 脚本示例 如何使用 解释 注意事项 QT工具实现 其他资源 实现原理 使用WebView组件造一工具,工具可输入网页地址并显示网页内容及播放视频。把工具的代理设置指向mitmproxy的端口服务。配合使用mitmproxy的MITM技术,监…

7.8~7.10练习

目录 1.扑克牌游戏 2.链表基本功能的实现(单项链表) 3.移除链表元素力扣 4.反转链表力扣 5.链表的中间结点 5.返回倒数第k个节点​编辑 6.合并两个有序链表 7.链表基本功能的实现(双向链表) 8.链表分割 1.扑克牌游戏 public…

LightRAG:高效构建和优化大型语言模型应用的 PyTorch 框架

一、前言 随着大语言模型 (LLM) 的蓬勃发展,检索增强生成 (RAG) 技术作为一种将 LLM 与外部知识库结合的有效途径,受到了越来越多的关注。 然而,构建 LLM 应用的真正挑战在于开发者需要根据具体需求进行高度定制化,而现有的 RAG …

Vscode ssh远程连接Linux服务器登录时密码password无法输入

问题 最近在用Vscode远程连接Linux服务器时,在终端提示输入密码password的时候用键盘输入没有反应。 以为是键盘坏了,然后尝试复制粘贴没有用。 后来找到了原因以及解决方法,感谢原帖作者(原贴链接粘在下面) 原因 …

flutter 列表下拉框加搜索

1.使用控件搜索加下拉框dropdown_search: ^0.4.9和获取中文拼音lpinyin: ^1.1.1 2.加入中文查询和首字查询 在当中找到相应的packages,再在SelectDialog.dart当中加入引入拼音搜索 import package:lpinyin/lpinyin.dart; 更改匹配方法manageItemsByFilter使其可…

有必要把共享服务器升级到VPS吗?

根据自己的需求来选择是否升级,虚拟专用服务器 (VPS) 是一种托管解决方案,它以低得多的成本提供专用服务器的大部分功能。使用 VPS,您的虚拟服务器将与在其上运行的其他虚拟服务器共享硬件服务器的资源。但是,与传统的共享托管&am…

Oracle数据库加密与安全

Wallet简介: Oracle Wallet(即内部加密技术TDE( Transparent DataEncryption) TDE是 Oracle10gR2中推出的一个新功能,使用时要保证Oracle版本是在10gR2或者以上 Wallet配置: 1.创建一个新目录,并指定为Wallet目录 /home/oracle…

Python爬虫技术从去哪儿网获取旅游数据,对攻略进行可视化分析,提供全面的旅游攻略和个性化的出行建议

背景 随着信息技术的快速发展和互联网的普及,旅游行业也迎来了数字化和智能化的变革。去哪儿网作为中国领先的在线旅游平台之一,提供了丰富的旅游产品和服务,涵盖了机票、酒店、旅游度假等各个方面。用户通过去哪儿网可以方便地查询、预订和…

STM32HAL库+ESP8266+cJSON+微信小程序_连接华为云物联网平台

STM32HAL库ESP8266cJSON微信小程序_连接华为云物联网平台 实验使用资源:正点原子F407 USART1:PA9P、A10(串口打印调试) USART3:PB10、PB11(WiFi模块) DHT11:PG9(采集数据…

阿里云操作系统智能助手OS Copilot的实验测评报告

什么是OS Copilot 在老师的介绍下我了解到了阿里云OS Copilot这个产品,它是阿里云推出的一项基于人工智能技术的操作系统,设计用于阿里云Linux操作系统以及其他可能的云上操作系统环境,为用户提供智能化的系统管理和运维支持。 阿里云提供了…

Python数据分析-Excel和 Text 文件的读写操作

1.Excel和 Text 文件的读写操作 1. Text 文件读写包 import sys print(sys.argv[0]) print(__file__) print(sys.path[0]) qopen(sys.path[0] "\out.txt","w",encodingutf-8) q.write(这个是测试一下) q.close() print(done)open 语句可以打开的创建text…

【吊打面试官系列-ZooKeeper面试题】简述 Zookeeper 文件系统?

大家好,我是锋哥。今天分享关于 【简述 Zookeeper 文件系统?】面试题,希望对大家有帮助; 简述 Zookeeper 文件系统? Zookeeper 提供一个多层级的节点命名空间(节点称为 znode)。与文件系统不同的是,这些节…

白平衡说明

白平衡 相机白平衡的起源原理以及作用起源作用 白平衡的原理白平衡的类型应用说明 工业相机的白平衡效果对比一键白平衡的必要性一键白平衡的实现方式 相机白平衡的起源原理以及作用 起源 白平衡(White Balance, WB)概念的起源与色温理论密切相关。色温…

【eNSP模拟实验】单臂路由实现VLAN间通信

实验需求 如下图所示,辅导员办公室需要访问处在不同vlan的学生管理服务器的文件,那么如何实现两台终端相互通信呢?我们可以使用单臂路由的方式来实现。 单臂路由(router-on-a-stick)是指在路由器的一个接口上通过配置…

Spring Boot中@Async注解的使用及原理 + 常见问题及解决方案

😄 19年之后由于某些原因断更了三年,23年重新扬帆起航,推出更多优质博文,希望大家多多支持~ 🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志 🎐 个人CSND主页——Mi…

VMWare 下给Centos扩容

目录 参考文档背景介绍扩容查看当前文件磁盘信息增加一个存储分区创建物理卷把物理卷添加到卷组查看卷组名把物理卷并入卷组 对文件系统进行扩容搞定 参考文档 1、百度经验 2、CSDN 3、掘金 背景介绍 测试环境用VMWare 安装centos7,几年下来磁盘空间不够用了&…

【前端项目笔记】10 项目优化上线

项目优化上线 目标:优化Vue项目部署Vue项目(上线提供使用) 项目优化 项目优化策略: 生成打包报告:根据生成的报告发现问题并解决第三方库启用CDN:提高首屏页面的加载效率Element-UI组件按需加载路由懒加…

数据结构4.0——串的定义和基本操作

串的定义(逻辑结构) 串,即字符串(String)是由零个或多个字符组成的有序数列。 一般记为Sa1a2....an(n>0) 其中,S是串名,单引号括起来的字符序列是串的值;ai可以是字母、数字或其他字符;串中字符的个数n称为串的长度。n0时的…

unity 2020版本packManager没有AssetBundles

1.Packages->manifest.json打开manifest.json文件 2.添加"com.unity.assetbundlebrowser": "1.7.0", 保存即可