鱼C论坛

 找回密码
 立即注册
查看: 1099|回复: 4

[技术交流] (原创)手撸一个简化版std::allocator内存池,附源码及解释

[复制链接]
发表于 2019-7-19 15:51:09 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 花开自有丶花落 于 2019-8-26 23:47 编辑

直奔主题,对于本帖,代码中的大部分注释我已经删掉,取而代之的是再本帖中尽量说明
我将主要分为五大块来介绍,顺便为自己重新捋一下思路,代码中也有某些未完善的不足,仅供学习,转载请注明出处
另:第一次写这么长的东西,有些地方写的不好还请见谅
PS:后面还有点没写完,可能是因为有些长,写草稿有些显示不出来,先发出来看下
      发表后还是显示不出来,源码最下面,可以直接下载
  • 简介与使用
  • 一些宏与常量的说明
  • 各个类与函数的整体介绍
  • 每个函数内部的逻辑
  • 不足


一、简介
        主要解决的内存碎片问题,为了程序长期稳定运行,而付出代价(时间与空间)代码手动管理
        总体思路参照std::allocator,三个类以及一个联合体完成实现,另外已在一个近千行的多线程网络程序中测试(如要使用请自行决断)
        主要通过维护16个长度不同的自由链表,8字节,16字节,32字节等等,一直到128字节,每次申请通过找到最适合的长度,找到对应链表,由链表分配内存
        运行环境:vs2017
        使用,如下图,只需要简单的将其包含在main文件中的第一行,重载的new和delete即可替换掉默认的
       
FXR5M_ASCCHW(W8EH4L@1Z6.png
二、一些宏与常量的说明
        .h的头文件,首先看到的就是一下几行:
0K(N_16E{)E6L(7OK(E[@MP.png

       

  • _DEBUG宏定义
            宏定义_DEBUG,这个在vs中调试模式下会有这个宏,release下就不会定义这个宏
            这个宏的主要作用:
                    调式模式:xPrintf(...)替换为printf(__VA_ARGS__),输出调试信息
                    release模式:把xPrintf(...)替换为空,不会有输出信息
  • 两个常量(PS:如果看不懂这一块内容,可以先看第三块,遇到了在回来找)
            const int MA_MAX_LENGT = 128;       
                    这个128代表的是内存池最大管理的可分配长度,在本帖的内存池中,主要管理8,16,24,32,一直到MA_MAX_LENGT(128),等8的倍数的内存分配
                    不足8的倍数则向上取整,如new char[13],需要13字节内存空间,则为其分配16字节空间,回收时也由内存池回收
                    大于128的空间。就直接调用C的malloc()函数申请
                    为什么选128,因为std::allocato是这么做的,我也没有很多经验,不确定选取哪个界限,以内存池管理会有好的行为
            const int SIZEOFSHORT = sizeof(short);
                    这个常量一般都是2字节,只是用的比较多,不想在运行时多次计算,就放到这里。
  • 重载new与delete(PS:如果看不懂这一块内容,可以先看第三块,遇到了在回来找)
            只是在头文件中声明了重载new与delete,具体实现可在.cpp中找到,第三块和第四块内容也会说

三、各个类与函数的整体介绍
        本块是最重要的,主要自顶向下讲述三个与联合体间的交互,介绍顺序为class SHH_MA;        class free_lists;        class free_listor;        union obj;
  • 首先是各个类间的依赖关系(配合主要功能介绍食用第三小节)
    X767}JIUL%5O{A4SP}{XV)P.png
  • 然后介绍一个各个类的主要作用
            class SHH_MA;
                    SHH_MA是最上层的类,前缀是名字缩写喽,MA就是memory alloc(内存分配)的缩写了
                    SHH_MA为operator new和operator delete服务,也就是说operator new和operator delete是通过SHH_MA来分配内存的
                    operator new和operator delete直接通过SHH_MA分配和回收内存
            class free_lists;       
                    SHH_MA会持有一个class free_lists*的数组
                    free_lists是一个基类,class free_listor会继承它
                    free_lists提供一组接口,SHH_MA通过持有的 free_lists*数组操作free_listor对象
            class free_listor;
                    free_listor负责内存的实际分配和实际回收,是一个模板
            union obj;
                    obj为free_listor提供数据结构支持,也是一个模板
  • 最后是各个成员函数及数据的主要功能(PS:建议与依赖关系图配合食用)
            new和delete的重载
            因为重载的new和delete是最外层的调用,所以先说一下这个
            如图中,仅是通过SHH_MA::SHH_MA_OBJ()得到单例对象,然后调用其分配和回收内存的函数,更多的细节留给下层的类去处理
            SHH_MA::SHH_MA_OBJ()见下
           
    CCY76@$GANV673A{J3Z$PME.png

            class SHH_MA;
           

                                    数据:
                                    如下图
                                    一个free_lists*数组
                                    还有16个派生自free_lists的ree_listor,这16个free_listor分别负责维护不同大小内存的分配
                                    free_lists数组是一个映射数组,这个数组有MA_MAX_LENGT + 1,即129,索引为0到128,分别指向16个free_listor
                                    举例,freedom_list_map[1~8],存储指向free_listor<8> md_mem8的指针,这样比如说我这时候要申请一个5字节的空间
                                    直接用这个申请的长度5作为索引,freedom_list_map[5]的位置指向md_mem8,通过md_mem8的指针直接调用其分配内存函数
                                    这么做可以提高效率,空间换时间,就不用每次申请的长度调整到8的倍数
                                   
    `OJ}Q)5WA)A`R{0VV855_0C.png

                           

                                    函数:
                                    SHH_MA() { xPrintf("SHH_MA构造\n"); init(); }
                                    构造函数,在被声明为私有,是为了完成单例模式,因为整个程序只要有一个内存管理的就足够了,在构造内部调用了初始化函数

                                    SHH_MA(const SHH_MA& SHH_MA) = delete;
                                    SHH_MA& operator=(const SHH_MA& SHH_MA) = delete;
                                    复制构造和赋值被显示禁用防止拷贝

                                    static SHH_MA& SHH_MA_OBJ();
                                    该函数在内部构造一个本类对象,使用时,通过该函数返回本类在其内部的唯一个单例对象,然后调用其他函数

                                    void init();
                                    在内部初始化freedom_list_map数组,和16个不同的长度内存池

                                    void init_assit(int begin, int end, free_lists* p);
                                    辅助init函数初始化,尽在init内部调用

                                    void* request_memory(size_t size);
                                    分配内存,返回void指针,返回内存就够了,在内存上干什么与这里无关

                                    void return_memory(void *p);
                                    回收内存

                                   
    X@}W_DKDU5ZK0YG_CE@4Q13.png

                           



                                   
            class free_lists;       
           

                                    数据:无

                           

                                    函数:
                                    free_lists() {}
                                    virtual ~free_lists() {}
                                    构造析构略过

                                    virtual void init(int init_block_size) { xPrintf("base init\n"); }                                        // 初始化
                                    virtual void* request_memory() { xPrintf("base request\n"); return nullptr; }                // 申请内存
                                    virtual void return_memory(void* p) { xPrintf("base return\n"); }                                // 归还内存
                                    提供的虚函数接口, free_listor则会再次实现这些接口,这样通过在SHH_MA的free_lists数组,就可调用free_listor中对应的函数,还在SHH_MA易于管理
                                   
    WX~NZ1T$_0}{IXDXXSAF_NO.png

                                   
                           

            class free_listor:public free_lists{};
           

                                    数据:
                                            int md_init_block_size;                // 该链表的初始区块数量,举例:比如当前模板参数被8初始化,那么这个对象就有md_init_block_size块8字节的内存
                                            int md_block_size;                        // 链表没有可分配的空间时,会调用填充函数,再一次申请连续的内存块,初始化成链表,链入表中
                                            obj<n>* md_header;                        // 链表的头
                                            int md_one_offset;                        // 一次偏移的距离,为了防止多次计算
                                            std::mutex md_mutex;                // 多线程安全的保证
                                            char* POOL;                                // 初始化时,申请的一大块连续内存的初始地址
                                   
    YT~O{0`$LL}X{`0}FI@2IY8.png

                           

                                    函数:
                                            free_listor();
                                            构造并没有干什么,只是将值初始化成0和空指针

                                            virtual void init(int init_block_size);
                                            初始化,主要就是申请大块内存,初始化成链表                                       

                                            virtual void* request_memory();
                                            申请内存

                                            virtual void return_memory(void* p);
                                            释放内存
                           
                                            void fill_memory();
                                            two_request_memory();
                                            这两个函数只会被void* request_memory调用,当request_memory函数管理的链表为空时,就会调用 fill_memory函数填空链表,增加可分配的块,
                                            接着two_request_memory();就会再次申请内存,如果two_request_memory()再次申请内存失败,会直接调用malloc分配内存,malloc再失败就抛出异常

                                   
    P109$I7JDWAQ(_0G_5AK%37.png

                           

            union obj;
           

                                    数据:
                                    为什么这个联合体是个模板,其实这个联合体才是整个最最核心的地方
                                    md_data是为了定义整个区块的长度
                                    而md_next则是为了方便把内存块转换成对象指针方便操作
                                    其中md_data就是整个内存管理中最最精髓的地方,它既是链表,又是可分配的内存空间
                                    举例:初始化了一个obj<8>的对象,那么根据md_data,这个obj对象实例就有8字节长,这个md_data:
                                            在链表中:使用md_next类型,存储指向下一个节点的地址
                                            被分配出去后:存储要写入的数据,让我用一张图来说明
                                    md_header一开始指向地址为0的内存单元,而地址0中存储的数据是指向地址8的指针,而地址8开始存储的数据是指向 地址16的指针
                                    当我分配出去一块8字节的内存后,md_header变更为指向地址8的位置,而地址0被返回给调用者,调用者在地址0开始的地方覆盖,直接写数据
                                    地址0被归还回来的时候,接入链表,md_header又指向地址0,而地址0中的数据又被覆盖写为指向 地址8的指针
                                    另:这里可能会有一个疑问?为什么最小分配的内存单元是8字节,因为这是为了兼容32位和64位系统,32位下的指针是4字节,64位下指针8字节
                                          32下可能会浪费4字节的空间,但64位下,8字节却是刚刚好
                                   
    7R(]4UM]IO@I[SDBWU6OL6B.png

                                   
    97I$`G2T]N)0Y9Q9%)5UYE9.png
                           
                           

                                    函数:无
                           



四、每个函数内部的逻辑
五、不足

SHH_MEMORY_ALLOO_2.rar

2.48 KB, 阅读权限: 5, 下载次数: 2

源码

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2019-7-19 16:09:58 | 显示全部楼层
没人吗
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2019-7-23 17:39:19 | 显示全部楼层
变量info_stand是不是标记,代码中的意思标记是用来记录一个单位长度的内存块的大小的,但是好像没什么作用,因为不会连续分配内存块,会不会浪费了内存空间。回收内存的时候最好验证一下是不是内存池中的地址。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2019-7-24 19:58:48 | 显示全部楼层
本帖最后由 花开自有丶花落 于 2019-7-24 20:02 编辑
歪头 发表于 2019-7-23 17:39
变量info_stand是不是标记,代码中的意思标记是用来记录一个单位长度的内存块的大小的,但是好像没什么作用 ...


这个标记是为了区分是哪个自由链表的,比如说n被初始化为8,然后多申请两字节的空间,转换成short,把这个short赋值成8,这样在归还的时候,通过这个short来判断是属于哪个free_listor的,如果是直接malloc分配的,也会多申请一个short,把哪个short赋值为-1代表不是内存池分配的,直接free
为了解决内存碎片当然需要付出一些代价
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2019-7-24 21:39:32 | 显示全部楼层
花开自有丶花落 发表于 2019-7-24 19:58
这个标记是为了区分是哪个自由链表的,比如说n被初始化为8,然后多申请两字节的空间,转换成short,把 ...

有道理,思考了一下,确实没想到更好的办法。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-5-11 23:37

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表