• AntDB内存管理之内存上下文


    1.主题说明

    AntDB的内存管理在开发时,使用了内存上下文机制来实现内存管理。本文就从AntDB的内存上下文机制出发,解析内存上下文的实现原理。

    AntDB的代码中,涉及到内存的处理时,经常会看到下面这样的代码。

    图1:切换内存上下文示例

    以及图2所示的代码。

    图2:在内存上下文中申请内存

    这与平常开发C/C++程序的内存操作方式不太一样,多多少少会让人产生一些疑惑:

    • 内存操作前后的MemoryContextSwitchTo是什么意思?
    • 这个内存上下文为什么要切来切去的?什么时候需要切换?
    • palloc函数只是调了一个函数指针,实际上由什么函数来实现的?
    • 为什么经常只能看到内存申请操作,却看不到释放操作?内存申请到哪去了?不释放没问题吗?

    接下来就为各位小伙伴慢慢解析一下这个内存上下文。

    2.内存上下文(MemoryContext)是什么

    内存上下文是一种内存管理机制。通俗一点来说,内存上下文可以看作是内存块和操作该内存块的方法的一个集合。举个例子,有一种内存上下文叫MemoryContextA,如果用户切换到MemoryContextA的话,那么接下来的操作至少会遵循以下2个规则。

    1)申请的内存都是属于MemoryContextA,并且这些内存也会随着MemoryContextA的删除而被删除掉。

    2) 操作内存(申请、释放、重分配等等)都是由MemoryContextA定义的方法来执行的。

    AntDB中存在很多内存上下文,在它们之间会建立如图3所示的树形关系。(同一层之间其实还有兄弟关系,本图为了突出树形的层级关系,未在图中标识出兄弟关系。)

    图3:内存上下文树形关系

    3.为什么要引入内存上下文

    C/C++程序的开发者对内存操作这一部分(特别是内存的申请/释放操作)肯定深有体会,其中内存泄漏的问题更是家常便饭。尤其是当我们使用传统的内存操作方式来开发一个大型的软件(系统)时,保证内存操作不会出问题是相当有挑战的,并且这也会造成更多的开发成本。

    AntDB引入内存上下文机制后,可以使得我们不用在意每一处内存的申请/释放,也让内存管理工作变得更加清晰、方便、可靠。

    4.内存上下文机制是怎么实现的

    下文将针对内存上下文机制进行代码说明。本次以AntDB的代码为例,来解析内存上下文的实现方式。

    4.1 最基础的数据结构

    MemoryContextData和MemoryContextMethods是内存上下文机制里最基础的2个数据结构。定义如下图4所示(只针对特定成员进行说明,其他的变量说明可以参照代码)。

    图4:MemoryContextData和MemoryContextMethods

    ※1:这个结构体只是个指针的集合而已,并没有实现。开发者可以根据这个框架自己提供一套实现方式。AntDB已经实装的一套通用的实现:AllocSetMethods。另外AntDB也提供了GenerationMethods和SlabMethods的实现方式,但这2个需要在特定的使用场景下进行使用。接下来的说明都是以AllocSetMethods为前提的。

    4.2 通用的实现AllocSetContext

    AntDB提供了一个通用的内存上下文实现:AllocSetContext。代码中的内存操作几乎都是通过这个类型的内存上下文来处理的。

    首先,我们了解一下AllocSetContext的定义,如下图5所示。

    图5:AllocSetContext

    4.2.1 内存操作方法

    其次,我们看一下header成员。从图5可以看出来,内存操作相关的方法都存放在了header成员里;另外,构建内存上下文的树形关系用的成员变量,比如parent,firstchild等,也都在header里面。

    内存上下文可以提供的内存操作有以下几种。

    • AllocSetAlloc:内存申请
    • AllocSetFree:内存释放
    • AllocSetRealloc:内存重分配
    • AllocSetReset:内存上下文重置
    • AllocSetDelete:内存上下文删除
    • AllocSetGetChunkSpace:检查内存片的大小
    • AllocSetIsEmpty:检查内存上下文是否为空
    • AllocSetStats:获取内存上下文的状态信息
    • AllocSetCheck:检查所有内存

    4.2.2 内存块

    接下来,我们就了解一下内存上下文的实际内存在哪里。

    图6:内存上下文的实际内存块

    blocks成员是一个链表,内存上下文的内存都放在blocks成员里。内存上下文申请内存时是以block(也可称作大内存)的方式,一次申请一块大内存。申请的block会加入到AllocSetContext的blocks链表中。

    内存上下文提供的内存申请函数AllocSetAlloc,是以chunk(内存片,也可称作小内存)的方式分配内存给使用者。即该操作会把block根据需要分成一片一片,然后把内存片的地址提供给调用者。我们调用函数palloc得到的就是这个内存片的地址。

    4.2.3 内存上下文实例的整体结构

    上文已经分别介绍完了内存上下文的2个重要成员header和blocks。据此,我们可以在脑中画出一个内存上下文实例大概的全貌。请参照下图7。

    图7:内存上下文实例的整体结构

    前面树形图中(图3)看到的TopMemoryContext、ErrorContext等,从内存角度看的话,就是图7所显示的模样。

    5. 如何使用内存上下文

    使用内存上下文之前,我们需要先对其进行创建。AntDB启动时已经创建并初始化好了部分内存上下文,例如:TopMemoryContext。这个TopMemoryContext是所有内存上下文的父节点或者祖先节点。一般我们创建的内存上下文都在TopMemoryContext的子层以下。创建完之后,我们便可以通过palloc/palloc0使用该内存上下文,且使用完成之后可以释放内存上下文。

    5.1 创建内存上下文

    我们通过AllocSetContextCreate来创建内存上下文,这是一个宏定义。实际处理是由AllocSetContextCreateInternal来完成的。

    图8:AllocSetContextCreateInternal函数定义

    ・parent:我们需要指定父节点的内存上下文。根据程序适当的设定。

    ・name:内存上下文的名称。

    ・minContextSize:内存上下文的最小尺寸。

    ・initBlockSize:内存上下文的初始尺寸。

    ・maxBlockSize:内存上下文的最大尺寸。

    最后我们需调用MemoryContextCreate函数创建内存上下文。

    5.2 在内存上下文中使用内存

    在申请内存之前我们需要考虑:当前内存应该在哪一个内存上下文申请。不同的内存上下文,使用目的、生命周期都是不一样的。决定好内存上下文之后,我们可调用MemoryContextSwitchTo函数切换至目标内存上下文。

    MemoryContextSwitchTo函数的作用是切换至目标上下文,并返回当前的内存上下文。一般使用方法请参照:

    1) 当前内存上下文 = MemoryContextSwitchTo(目标内存上下文)。

    2) 进行内存申请等操作。这时候申请的内存都是在目标上下文中。

    3) 根据自己的代码处理逻辑。如果有需要,请及时切换回当前内存上下文。MemoryContextSwitchTo(当前内存上下文)。

    内存上下文的切换不当,或者切换不及时的话,都可能会带来预料之外的后果,甚至直接导致程序崩溃。举个很简单的例子说明一下,如图9所示。

    图9:内存上下文切换不当示例

    一旦决定好所需要用的内存上下文之后,我们就可以调用内存分配函数palloc/palloc0来申请内存了。注意一点,palloc0会在申请完内存之后把内存全部初始化成0,而palloc申请的内存的内容是不确定的。这两个函数的最终都是由AllocSetAlloc来实现。

    内存使用完之后,可以调用pfree来释放。这个函数指向的是AllocSetFree函数。在AntDB中,palloc的内存也并不是一定要调用pfree来释放内存。内存的释放工作可以留到内存上下文的释放阶段执行。

    当然还有其他内存相关的各种操作,realloc、reset等等。它们指向的函数在前面的4.2.1章节都已列出来了。

    5.3 释放内存上下文

    我们可以调用MemoryContextDelete来删除不再使用的内存上下文。如果仅仅是想释放内存上下文中的某些内存片的话,可以调用pfree来释放部分内存。注意一点,这个pfree操作只是把内存还给内存上下文,并不是还给操作系统。AntDB还提供了一个内存重置reset的功能,这个reset可以释放所有内存块(除了特别设置成保留的内存块,保留内存块内容会被清除)。

    6. 总结

    本篇文章给大家简单地介绍了内存上下文的基本概念,希望大家阅读之后对内存上下文有些基本的了解,以后再看到类似的代码不会过于陌生。同时,对本文一开始提出的几点疑惑,大家心中应该也找到了答案。

    当然,本文只介绍了内存上下文的部分内容,还有很多的知识没有在文中进行阐述,比如:结构体中其他的成员变量干什么的;创建内存上下文是个什么流程;申请内存是个什么流程,有什么算法等等。还有,内存上下文有什么坑;有什么参数能控制内存上下文大小吗;AntDB对内存上下文的持续改进……我们会在接下来的文章里继续和大家慢慢分享。先有概念,再有细节。本文就是建立概念的过程,来为后面讲解细节奠定基础。

    最后,给各位数据库爱好者、技术爱好者一点建议。技术不是通过阅读一些技术文章就能提升的,我们需要自己动手实验、调试,才能把看到的内容转化成自己的知识。欢迎大家关注我们AntDB,跟我们一起在数据库的世界里共同探索,一起成长。

    关于AntDB数据库

    AntDB数据库始于2008年,在运营商的核心系统上,为全国24个省份的10亿多用户提供在线服务,具备高性能、弹性扩展、高可靠等产品特性,峰值每秒可处理百万笔通信核心交易,保障系统持续稳定运行近十年,并在通信、金融、交通、能源、物联网等行业成功商用落地。

  • 相关阅读:
    EPLAN小知识——如何在费斯托(FESTO)官网下载EPLAN部件
    MySQL 上亿数据查询优化:策略与实践
    你真的了解Java异常处理机制吗?
    Java设计模式之桥接模式
    天梯赛 L2-046 天梯赛的赛场安排
    算法开篇——数组
    基于51单片机NEC协议红外遥控发送接收仿真设计( proteus仿真+程序+原理图+报告+讲解视频)
    java 实现事件监听EventListener的方式详解及分析
    计算机毕业设计Java机票实时比价系统(源码+系统+mysql数据库+lw文档)
    Prism 入门03,模块化介绍使用
  • 原文地址:https://blog.csdn.net/weixin_44518445/article/details/127649809