Nginx 高性能内存池 ----【学习笔记】

跟着这篇文章学习:

c++代码实现一个高性能内存池(超详细版本)_c++ 内存池库_linux大本营的博客-CSDN博客https://blog.csdn.net/qq_40989769/article/details/130874660以及这个视频学习:

nginx的内存池_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1WV4y1u7eM/?spm_id_from=333.337.search-card.all.click&vd_source=a934d7fc6f47698a29dac90a922ba5a3小编跟着这篇文章和这个视频学习收获匪浅,也提炼一些自己觉得关键的地方出来。

(一)内存池的应用场景

#include <unistd.h>
int brk(void *addr);
void *sbrk(intptr_t increment);
DESCRIPTION
       brk()  and  sbrk() change the location of the program break, which defines the end of the process's data
       segment (i.e., the program break is the first location after the end of the uninitialized data segment).
       Increasing  the  program  break has the effect of allocating memory to the process; decreasing the break
       deallocates memory.

       brk() sets the end of the data segment to the value specified by addr, when that  value  is  reasonable,
       the system has enough memory, and the process does not exceed its maximum data size (see setrlimit(2)).

       sbrk()  increments  the  program's data space by increment bytes.  Calling sbrk() with an increment of 0
       can be used to find the current location of the program break.
	   

描述:
	brk()和sbrk()改变程序断点的位置,它定义了进程数据的结束段(即,程序断点是未初始化数据段结束后的第一个位置)。
	增加程序断点的效果是为进程分配内存;减少断裂重新分配内存。

	brk()将数据段的结束设置为addr指定的值,如果该值是合理的。系统有足够的内存,并且进程没有超过其最大数据大小(参见setrlimit(2))。

	sbrk()以增量字节增加程序的数据空间。以0的增量调用sbrk(),可用于查找程序断点的当前位置。

为什么我们需要内存池?

(1)sbrk不是系统调用,是C库函数。系统调用通常提供一种最小功能,而库函数通常提供比较复杂的功能。在Linux系统上,程序被载入内存时,内核为用户进程地址空间建立了代码段、数据段和堆栈段,在数据段与堆栈段之间的空闲区域用于动态内存分配。C语言的动态内存分配基本函数是malloc(),在Linux上的基本实现是通过内核的brk系统调用。malloc分配的内存是位于堆中的,并且没有初始化内存的内容,因此基本上malloc之后,调用函数memset来初始化这部分的内存空间.这段文字截取自以下文章:

Linux中brk()系统调用,sbrk(),mmap(),malloc(),calloc()的异同【转】_mob604756ff20da的技术博客_51CTO博客https://blog.51cto.com/u_15127651/4611036(2)频繁地malloc 和 free会消耗系统资源

        malloc() 并不是系统调用,也不是运算符,而是 C 库里的函数,用于动态分配内存。

        malloc 申请内存的时候,会有两种方式向操作系统申请堆内存:

                 方式一:通过 brk() 系统调用从堆分配内存

                方式二:通过 mmap() 系统调用在文件映射区域分配内存;

深入linux操作系统-malloc到底如何分配内存? - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/581863694

(3)预先在用户态创建一个缓存空间,作为内存池。申请内存的时候,不走系统调用,可实现超级加速内存管理。

 (4)频繁地malloc和free,由于malloc的地址是不确定的,容易产生内存碎片

例如:需要4个字节的空间,却因为malloc得到位置随机分配的位置很尴尬,就导致有2+2的空间无法使用。

(二)Nginx内存池的特点

线程池、连接池:核心思想都是对系统的资源调度起一个缓冲的作用。实现大同小异,基本是两队列+中枢的架构。

内存池:可灵活变通,根本原因是面向的实际业务不同。

在Nginx的服务器中,每当有新的客户端连接,就为其创建一个内存池,用于recv和send的缓冲区buffer。

Nginx的内存池的特点概括:

① 有两种内存分配方式,分别是大内存和小内存。可用不同的数据结构来存储。为了适应客户端不同的请求,如果只是一些简单的表单,就用小内存,如果是上传下载大文件,就用大内存。

② 不像线程池会回收利用所有线程,Nginx的内存池不回收小内存的buffer,只回收大内存的buffer

针对Nginx内存池在实际业务中应用分析:

① 每个内存池都对应一个客户,那么一个客户端产生的小内存碎片自然不会太多,即使不会收,也不会有太大的代价。

② Nginx是典型的将不同客户端分发到多进程的网络模型。

③ tcp本身就有keep-alive机制,超过一定时间就会断开。如果因此连接断开,进程结束,从而对应的内存池会释放,相当于一次性回收所有的大小内存。

④ Nginx很有必要回收连接中大内存,因为其占用空间大。

(三)底层数据结构

(1)实现核心的,小内存

首先整个内存池pool中有两条链,一条是big block链,一条是small block链

small block 数据结构如下:

class small_block{
public:
    char* cur_usable_buffer; // 指向该block的可用buffer的首地址
    char* buffer_end;        // 指向该block的buffer的结尾地址
    small_block* next_block; // 指向block链的下一个small block
    int no_enough_times;     // 每次分配内存,都要顺着small block链,找链中的每个小缓冲区,看是否有足够分配的内存,如果在该block没找到,就会将该值+1,代表没有足够空间命中的次数
};

对于small block,它很特殊,不同于单独的block对象,其后面跟着的buffer,r,当链中所有的小缓冲区都不够位置分配到新的空间时,就会一次性创建空间大小为small_block + buffer_capacity新的small block。

为何不先malloc一个small_block,再malloc一个small_buffer呢?

那样会产生两块随机地址,如果一次性创建空间大小为small_block + buffer_capacity 的新的small block,可以让small_block和small_buffer连在一起形成一个整块,方便内存管理。

如何拿到small_buffer的首地址指针呢?

只要有了small_block的指针,自然也就可以拿到buffer的首地址指针。

即:buffer_head_ptr = (char*)small_block + sizeof(small_block);

small_block中各指针的指向:

 然后是整个内存池pool的数据结构

class memory_pool{
public:
    /*
    对于Nginx的内存池,每个small buffer的大小都是一样的,
    所以该值代表了small buffer的容量,在创建内存池的时候作为参数确定
    */
    size_t small_buffer_capacity;
    /*
    每次要分配小内存的时候,并不会从头开始找合适的空间。
    而是从这个指针指向的small_block开始找。
    */
    small_block* cur_usable_small_block;
    /*big block链的链头*/
    big_block* big_block_start;
    /*small block的链头*/
    small_block small_block_start[0];

    static memory_pool *createPool(size_t capacity);
    static void destroyPool(memory_pool *pool);
    static char* createNewSmallBlock(memory_pool* pool,size_t size);
    static char* mallocBigBlock(memory_pool* pool,size_t size);
    static void* poolMalloc(memory_pool* pool,size_t size);
    static void freeBigBlock(memory_pool* pool,char* buffer_ptr);
};

【注意】:该类的最后一个成员small_block_start,其为一个长度为0的数组。

柔性数组链接教程:【C语言】柔性数组_南木_N的博客-CSDN博客https://blog.csdn.net/Zhuang_N/article/details/128863104

(2)实现大内存

big block的数据结构如下:

class big_block{
public:
    char* big_buffer;      // 大内存buffer的首地址
    big_block* next_block; // 因为big block也是链式结构,指向下一个big block
};

big block最简单,其big_block和big_buffer的地址就是分开的,不会连在一起的。

注意:big_block本身算是一个小内存,那就不应该还是用随机地址,应该保存在内存池内部的空间。而big block是分配在小内存池中的,所以在之后会讲到的销毁池子中可以和小内存一并free掉。

 (四)相关api细节

(1)创建线程池 createPool:

为何Nginx要使用柔性数组:small_block_start?

【原因】Nginx希望创建内存池pool的时候,就创建第一个small_block,且同步建立一个small_buffer,让这三个对象的内存连起来。柔性数组的意义在于:不想通过上文提到的指针加法来找到small_block的首地址,可以利用柔性数组在memory_pool保留一个指针锚点来指向第一个small_block。

如何让柔性数组作为锚点? 使用一个0长度的数组作为锚点

【优点】这样malloc整段内存,small_block就会接在memory_pool的后面,且在small_block_start的形式成为pool的成员,实际上small_block_start长度为0是不占pool的内存空间的。

【注意】为什么使用静态成员函数也是这个原因,使用柔性数组必须保证其位置定义在整个类的内存空间的末尾,静态函数虽然在类中声明,但是实际会存放在静态区中保存,不占用类的内存。

//-创建内存池并初始化,api以静态成员(工厂)的方式模拟C风格函数实现
//-capacity是buffer的容量,在初始化的时候确定,后续所有小块的buffer都是这个大小。
memory_pool* memory_pool::createPool(size_t capacity) {
    //-我们先分配一大段连续内存,该内存可以想象成这段内存由pool+small_block_buffers三个部分组成,
    //-为什么要把三个部分(可以理解为三个对象)用连续内存来存,因为这样整个池看起来比较优雅,各部分地址不会天女散花地落在内存的各个角落。
    size_t total_size = sizeof(memory_pool) + sizeof(small_block) + capacity;
    void* temp = malloc(total_size);
    memset(temp,0,total_size);

    memory_pool* pool = (memory_pool*)temp;
    fprintf(stdout,"pool address:%p\n",pool);
    //-此时temp是pool的指针,先来初始化pool对象
    pool->small_buffer_capacity = capacity;
    pool->big_block_start = nullptr;
    pool->cur_usable_small_block = (small_block*)(pool->small_block_start);

    //-pool+1的1是整个memory_pool的步长,别弄错了,此时sbp是small_blcok的指针
    small_block* sbp = (small_block*)(pool + 1);
    fprintf(stdout,"first small block address:%p\n",sbp);
    //-初始化small_block对象
    sbp->cur_usable_buffer = (char*)(sbp + 1);
    fprintf(stdout,"first small block buffer address:%p\n",sbp->cur_usable_buffer);
    sbp->buffer_end = sbp->cur_usable_buffer + capacity;//-第一个可用的buffer就是开头,所以end=开头+capacity
    sbp->next_block = nullptr;
    sbp->no_enough_times = 0;

    return pool;
};

(2)代替malloc的分配内存的接口:poolMalloc

根据申请内存的size,判断需要申请的内存是一个大内存还是小内存

① 大内存就走mallocBigBlock这个api

② 小内存,就从cur_usable_small_block这个small block开始找足够的空间去分配内存。

为什么不是从small block链的开头开始寻找?

【原因】因为大概率cur_usable_small_block之前的所有small block都已经分配完了,所以为了提高命中效率,需要这样一个指针指向寻找的开始。

对于每个small_block,如何判断其是否能够分配?

small buffer的剩余容量 = buffer_end - cur_usble_buffer,用这个值和size对比,判断是否能够分配。

 剩余空间分配会出现两种情况:

情况一:足够空间能够被分配,从cur_usable_buffer开始分配size大小的空间,并返回这段空间的首地址,同时更新cur_usable_buffer指向新的剩余空间。

情况二:一直找到链的末尾都没有足够的size大小的空间,那就需要创建新的small block,走createNewSmallBlock 这个api。

//-分配内存
void* memory_pool::poolMalloc(memory_pool* pool,size_t size) {
    //-先判断要malloc的是大内存还是小内存
    if(size < pool->small_buffer_capacity) {//-如果是小内存
        //-从cur small block开始寻找
        small_block* temp = pool->cur_usable_small_block;
        do {
            //-判断当前small block的buffer够不够分配
            //-如果够分配,直接返回
            if(temp->buffer_end - temp->cur_usable_buffer > size) {
                char* res = temp->cur_usable_buffer;
                temp->cur_usable_buffer = temp->cur_usable_buffer + size;
                return res;
            }
            temp = temp->next_block;
        }while(temp);
        //-如果最后一个small block都不够分配,则创建新的small block;
        //-该small block在创建后,直接预先分配size大小的空间,所以返回即可
        return createNewSmallBlock(pool,size);
    }
    //-分配大内存
    return mallocBigBlock(pool,size);
}

(3)创建新的小内存块:createNewSmallBlock

创建一个smallblock和连带的buffer

这样创建的目的:为了分配size空间,所以初始化后,便预留size大小的buffer,对cur_usable_buffer进行更新。

【注意】每次到了创建新的small block的环节,就意味着目前链上的small buffer空间已经分配得差不多了,可能需要更新cur_usable_small_block,这就需要用到small block的no_enough_times成员,将cur_usable_small_block开始的每个small block的该值++,Nginx设置的经验值阈值是4,超过4,意味着该block不适合再成为寻找的开始了,需要往后继续尝试。

(4)分配大内存空间:mallocBigBlock

如果size超过了预设的capacity,就需要分配大内存空间,可以走mallocBigBlock 这个api

同样也是一个链式查找的过程,只不过比查找small block更快更粗暴

【注意】

① big block 链没有类似cur_usable_small_blcok这样的节点,

② big block 链只要从头开始遍历,如果有空buffer就返回该block,如果超过3个还没找到(同样是Nginx的经验值)就直接不找了,创建新的big block。

 【注意】 

① big_buffer是个大内存,所以其是个malloc的随机地址

② big_block本身是一个小内存,那就不应该还是用随机地址,应该保存在内存池内部的空间。方便回收。

③ 套娃的内存池 poolMalloc 操作,用来分配big_blcok的空间

//-分配大块的内存
char *memory_pool::mallocBigBlock(memory_pool* pool,size_t size) {
    //-先分配size大小的空间
    void* temp = malloc(size);
    memset(temp,0,size);

    //-从big_block_start开始寻找,注意big_block是一个栈式链,插入新元素是插入到头结点的位置
    big_block* bbp = pool->big_block_start;
    int i = 0;
    while (bbp) {
        if(bbp->big_buffer == nullptr) {
            bbp->big_buffer = (char *)temp;
            return bbp->big_buffer;
        }
        if(i > 3) {
            break;//-为了保证效率,如果找三轮还没找到有空buffer的big_block,就直接建立新的big_block
        }
        bbp = bbp->next_block;
        ++i;
    }
    
    //-创建新的big_block,这里比较难懂的点,就是Nginx觉得big_block的buffer虽然是一个随机地址的大内存
    //-但是big_block本身算是一个小内存,那就不应该还是用随机地址,应该保存在内存池内部的空间。
    //-所以这里有个套娃的内存池malloc操作
    big_block* new_bbp = (big_block*)memory_pool::poolMalloc(pool,sizeof(big_block));
    //-初始化
    new_bbp->big_buffer = (char*)temp;
    new_bbp->next_block = pool->big_block_start;
    pool->big_block_start = new_bbp;

    //-返回分配内存的首地址
    return new_bbp->big_buffer;
}

(5)释放大内存:freeBigBlock:

big block是一个链式结构,可以从这个链的开头进行遍历,一直到找到对应的buffer位置,并free掉。

//~释放大内存的buffer,由于是一个链表,所以,确实,这是一个效率最低的api了
void memory_pool::freeBigBlock(memory_pool* pool,char* buffer_ptr) {
    big_block* bbp = pool->big_block_start;
    while (bbp)
    {
        if(bbp->big_buffer == buffer_ptr) {
            free(bbp->big_buffer);
            bbp->big_buffer = nullptr;
            return;
        }
        bbp = bbp->next_block;
    }
    
}

(6)销毁池子:destroyPool:

整个内存池pool中有两条链,一条是big block链,一条是small block链分别沿着这两条链去free掉内存即可。

【注意】

① 大内存的buffer和big block不是一起malloc的,所以只需要free掉buffer。

② big block是分配在小内存池中,之后free掉小内存的时候会顺带一起free掉。

③ small链的free不是从第一个small block开始的,而是从第二个small block。

如图所示,第一个small block的空间是和pool一起malloc出来的,不需要free,只要最后的时候free pool就会一起释放掉。

//-销毁内存池
void memory_pool::destroyPool(memory_pool * pool){
    //-销毁大内存
    big_block * bbp = pool->big_block_start;
    while(bbp){
        if(bbp->big_buffer){
            free(bbp->big_buffer);
            bbp->big_buffer = nullptr;
        }
        bbp = bbp->next_block;
    }
    //-为什么不删除big_block节点?因为big_block在小内存池中,等会就和小内存池一起销毁了

    //-销毁小内存
    small_block * temp = pool -> small_block_start->next_block;
    while(temp){
        small_block * next = temp -> next_block;
        free(temp);
        temp = next;
    }
    free(pool);
}

(7)测试代码

int main(){
    memory_pool * pool = memory_pool::createPool(1024);
    //-分配小内存
    char*p1 = (char*)memory_pool::poolMalloc(pool,2);
    fprintf(stdout,"little malloc1:%p\n",p1);
    char*p2 = (char*)memory_pool::poolMalloc(pool,4);
    fprintf(stdout,"little malloc2:%p\n",p2);
    char*p3 = (char*)memory_pool::poolMalloc(pool,8);
    fprintf(stdout,"little malloc3:%p\n",p3);
    char*p4 = (char*)memory_pool::poolMalloc(pool,256);
    fprintf(stdout,"little malloc4:%p\n",p4);
    char*p5 = (char*)memory_pool::poolMalloc(pool,512);
    fprintf(stdout,"little malloc5:%p\n",p5);

    //-测试分配不足开辟新的small block
    char*p6 = (char*)memory_pool::poolMalloc(pool,512);
    fprintf(stdout,"little malloc6:%p\n",p6);

    //-测试分配大内存
    char*p7 = (char*)memory_pool::poolMalloc(pool,2048);
    fprintf(stdout,"big malloc1:%p\n",p7);

    char*p8 = (char*)memory_pool::poolMalloc(pool,4096);
    fprintf(stdout,"big malloc2:%p\n",p8);

    //-测试free大内存
    memory_pool::freeBigBlock(pool, p8);

    //-测试再次分配大内存(我这里测试结果和p8一样)
    char*p9 = (char*)memory_pool::poolMalloc(pool,2048);
    fprintf(stdout,"big malloc3:%p\n",p9);

    //-销毁内存池
    memory_pool::destroyPool(pool);

    exit(EXIT_SUCCESS);
}

(8)常见的两个问题

  • 不用考虑多线程加锁么?

【原因】为了保证高可靠性和可用性,Nginx不是多线程模型,而是多进程,这个池只给一个连接独享,如果是多线程,把池子做成无锁队列即可

  • 为什么Nginx不回收小内存?

【原因】① 若创建一个链表pool_freelist专门存这些回收的小内存,从pool_freelist找因为回归顺序不定,内存大小不一,想找到适合自己的大小的块就要一直遍历整个链表,效率就不佳。

【原因】② Nginx 可能觉得小内存本身并不怎么占空间,而且回收还影响速度,再加上它又不是在主进程用的内存池,而且在子进程给每个连接配的内存池,等连接断开,大小内存都回归了,也不会内存泄漏,会更合理。

(9)完整代码

#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

using namespace std;

class small_block{
public:
    char* cur_usable_buffer; // 指向该block的可用buffer的首地址
    char* buffer_end;        // 指向该block的buffer的结尾地址
    small_block* next_block; // 指向block链的下一个small block
    int no_enough_times;     // 每次分配内存,都要顺着small block链,找链中的每个小缓冲区,看是否有足够分配的内存,如果在该block没找到,就会将该值+1,代表没有足够空间命中的次数
};

class big_block{
public:
    char* big_buffer;      // 大内存buffer的首地址
    big_block* next_block; // 因为big block也是链式结构,指向下一个big block
};

class memory_pool{
public:
    /*
    对于Nginx的内存池,每个small buffer的大小都是一样的,
    所以该值代表了small buffer的容量,在创建内存池的时候作为参数确定
    */
    size_t small_buffer_capacity;
    /*
    每次要分配小内存的时候,并不会从头开始找合适的空间。
    而是从这个指针指向的small_block开始找。
    */
    small_block* cur_usable_small_block;
    /*big block链的链头*/
    big_block* big_block_start;
    /*small block的链头*/
    small_block small_block_start[0];

    static memory_pool *createPool(size_t capacity);
    static void destroyPool(memory_pool *pool);
    static char* createNewSmallBlock(memory_pool* pool,size_t size);
    static char* mallocBigBlock(memory_pool* pool,size_t size);
    static void* poolMalloc(memory_pool* pool,size_t size);
    static void freeBigBlock(memory_pool* pool,char* buffer_ptr);
};

//-创建内存池并初始化,api以静态成员(工厂)的方式模拟C风格函数实现
//-capacity是buffer的容量,在初始化的时候确定,后续所有小块的buffer都是这个大小。
memory_pool* memory_pool::createPool(size_t capacity) {
    //-我们先分配一大段连续内存,该内存可以想象成这段内存由pool+small_block_buffers三个部分组成,
    //-为什么要把三个部分(可以理解为三个对象)用连续内存来存,因为这样整个池看起来比较优雅,各部分地址不会天女散花地落在内存的各个角落。
    size_t total_size = sizeof(memory_pool) + sizeof(small_block) + capacity;
    void* temp = malloc(total_size);
    memset(temp,0,total_size);

    memory_pool* pool = (memory_pool*)temp;
    fprintf(stdout,"pool address:%p\n",pool);
    //-此时temp是pool的指针,先来初始化pool对象
    pool->small_buffer_capacity = capacity;
    pool->big_block_start = nullptr;
    pool->cur_usable_small_block = (small_block*)(pool->small_block_start);

    //-pool+1的1是整个memory_pool的步长,别弄错了,此时sbp是small_blcok的指针
    small_block* sbp = (small_block*)(pool + 1);
    fprintf(stdout,"first small block address:%p\n",sbp);
    //-初始化small_block对象
    sbp->cur_usable_buffer = (char*)(sbp + 1);
    fprintf(stdout,"first small block buffer address:%p\n",sbp->cur_usable_buffer);
    sbp->buffer_end = sbp->cur_usable_buffer + capacity;//-第一个可用的buffer就是开头,所以end=开头+capacity
    sbp->next_block = nullptr;
    sbp->no_enough_times = 0;

    return pool;
};

//-销毁内存池
void memory_pool::destroyPool(memory_pool* pool) {
    //-销毁大内存
    big_block* bbp = pool->big_block_start;
    while (bbp) {
        if(bbp->big_buffer) {
            free(bbp->big_buffer);
            bbp->big_buffer = nullptr;
        }
        bbp = bbp->next_block;
    }
    //-为什么不删除big_block节点?因为big_block在小内存池中,等会就和小内存池一起销毁了

    //-销毁小内存
    small_block* temp = pool->small_block_start->next_block;
    while (temp) {
        small_block* next = temp -> next_block;
        free(temp);
        temp = next;
    }
    free(pool);
}


//-当所有small block都没有足够空间分配,则创建新的small block并分配size空间,返回分配空间的首指针
char* memory_pool::createNewSmallBlock(memory_pool* pool,size_t size) {
    //-先创建新的small block,注意还有buffer
    size_t malloc_size = sizeof(small_block) + pool->small_buffer_capacity;
    void* temp = malloc(malloc_size);
    memset(temp,0,malloc_size);

    //-初始化新的small block
    small_block* sbp = (small_block*)temp;
    fprintf(stdout,"new small block address:%p\n",sbp);
    sbp->cur_usable_buffer = (char*)(sbp + 1);//-跨越一个small_block的步长
    fprintf(stdout,"new small block buffer address:%p\n",sbp->cur_usable_buffer);
    sbp->buffer_end = (char*)temp + malloc_size;
    sbp->next_block = nullptr;
    sbp->no_enough_times = 0;
    //-预留size空间给新分配的内存
    char* res = sbp->cur_usable_buffer;//-存在个副本作为返回值
    sbp->cur_usable_buffer = res + size;

    //-因为目前得到所有small_block都没有足够的空间了。
    //-意味着可能需要更新内存池的cur_usable_small_block,也就是寻找的起点
    small_block* p = pool->cur_usable_small_block;
    while (p->next_block) {
        if(p->no_enough_times > 4) {
            pool->cur_usable_small_block = p->next_block;
        }
        ++(p->no_enough_times);
        p = p->next_block;
    }
    
    //-此时p正好指向当前pool中最后一个small_block,将新节点接上去
    p->next_block = sbp;

    //-因为最后一个block有可能no_enough_time > 4 导致 cur_usable_small_block更新成nullptr
    //-所以还需要判断一下
    if(pool->cur_usable_small_block == nullptr) {
        pool->cur_usable_small_block = sbp;
    }
    return res;//-返回新分配内存的首地址
}

//-分配大块的内存
char *memory_pool::mallocBigBlock(memory_pool* pool,size_t size) {
    //-先分配size大小的空间
    void* temp = malloc(size);
    memset(temp,0,size);

    //-从big_block_start开始寻找,注意big_block是一个栈式链,插入新元素是插入到头结点的位置
    big_block* bbp = pool->big_block_start;
    int i = 0;
    while (bbp) {
        if(bbp->big_buffer == nullptr) {
            bbp->big_buffer = (char *)temp;
            return bbp->big_buffer;
        }
        if(i > 3) {
            break;//-为了保证效率,如果找三轮还没找到有空buffer的big_block,就直接建立新的big_block
        }
        bbp = bbp->next_block;
        ++i;
    }
    
    //-创建新的big_block,这里比较难懂的点,就是Nginx觉得big_block的buffer虽然是一个随机地址的大内存
    //-但是big_block本身算是一个小内存,那就不应该还是用随机地址,应该保存在内存池内部的空间。
    //-所以这里有个套娃的内存池malloc操作
    big_block* new_bbp = (big_block*)memory_pool::poolMalloc(pool,sizeof(big_block));
    //-初始化
    new_bbp->big_buffer = (char*)temp;
    new_bbp->next_block = pool->big_block_start;
    pool->big_block_start = new_bbp;

    //-返回分配内存的首地址
    return new_bbp->big_buffer;
}

//-分配内存
void* memory_pool::poolMalloc(memory_pool* pool,size_t size) {
    //-先判断要malloc的是大内存还是小内存
    if(size < pool->small_buffer_capacity) {//-如果是小内存
        //-从cur small block开始寻找
        small_block* temp = pool->cur_usable_small_block;
        do {
            //-判断当前small block的buffer够不够分配
            //-如果够分配,直接返回
            if(temp->buffer_end - temp->cur_usable_buffer > size) {
                char* res = temp->cur_usable_buffer;
                temp->cur_usable_buffer = temp->cur_usable_buffer + size;
                return res;
            }
            temp = temp->next_block;
        }while(temp);
        //-如果最后一个small block都不够分配,则创建新的small block;
        //-该small block在创建后,直接预先分配size大小的空间,所以返回即可
        return createNewSmallBlock(pool,size);
    }
    //-分配大内存
    return mallocBigBlock(pool,size);
}

//~释放大内存的buffer,由于是一个链表,所以,确实,这是一个效率最低的api了
void memory_pool::freeBigBlock(memory_pool* pool,char* buffer_ptr) {
    big_block* bbp = pool->big_block_start;
    while (bbp)
    {
        if(bbp->big_buffer == buffer_ptr) {
            free(bbp->big_buffer);
            bbp->big_buffer = nullptr;
            return;
        }
        bbp = bbp->next_block;
    }
    
}

int main() {
    memory_pool* pool = memory_pool::createPool(1024);
    //-分配小内存
    char* p1 = (char*)memory_pool::poolMalloc(pool,2);
    fprintf(stdout,"little malloc1:%p\n",p1);
    char* p2 = (char*)memory_pool::poolMalloc(pool,4);
    fprintf(stdout,"little malloc2:%p\n",p2);
    char* p3 = (char*)memory_pool::poolMalloc(pool,8);
    fprintf(stdout,"little malloc3:%p\n",p3);
    char* p4 = (char*)memory_pool::poolMalloc(pool,256);
    fprintf(stdout,"little malloc4:%p\n",p4);
    char* p5 = (char*)memory_pool::poolMalloc(pool,512);
    fprintf(stdout,"little malloc5:%p\n",p5);

    //~测试分配不足开辟新的small block
    char* p6 = (char*)memory_pool::poolMalloc(pool,512);
    fprintf(stdout,"little malloc6:%p\n",p6);

    //~测试分配大内存
    char* p7 = (char*)memory_pool::poolMalloc(pool,2048);
    fprintf(stdout,"big malloc1:%p\n",p7);

    char* p8 = (char*)memory_pool::poolMalloc(pool,4096);
    fprintf(stdout,"big malloc2:%p\n",p8);

    //~测试free大内存
    memory_pool::freeBigBlock(pool,p8);

    //~测试再次分配大内存(我这里测试结果和p8一样)
    char* p9 = (char*)memory_pool::poolMalloc(pool,2048);
    fprintf(stdout,"big malloc3:%p\n",p9);

    //~销毁内存池
    memory_pool::destroyPool(pool);
    exit(EXIT_SUCCESS);
};

/*
    malloc
    栈内是比堆内存快
*/

运行效果: 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

go逆向符号恢复

前言 之前一直没怎么重视&#xff0c;结果发现每次遇到go的题都是一筹莫展&#xff0c;刷几道题练习一下吧 准备 go语言写的程序一般都被strip去掉符号了&#xff0c;而且ida没有相关的签名文件&#xff0c;没办法完成函数名的识别与字符串的定位&#xff0c;所以第一步通常…

C语言经典小游戏之三子棋(超详解释+源码)

“纵有疾风来&#xff0c;人生不言弃&#xff0c;风乍起&#xff0c;合当奋意向此生。” 今天我们一起来学习一下三子棋小游戏用C语言怎么写出来&#xff1f; 三子棋小游戏 1.游戏规则介绍2.游戏准备3.游戏的实现3.1生成菜单3.2游戏的具体实现3.2.1初始化棋盘3.2.2打印棋盘3.2…

TypeScript【enum 枚举】

导语 在 TypeScript 中&#xff0c;新增了很多具有特性的一些数据类型处理方法&#xff0c;enum 【枚举】就是其中&#xff0c;很具有代表性的一种&#xff0c;所以本章节就来聊聊 在 TypeScript 中如何去运用 enum 【枚举】。 枚举的概念&#xff1a; 枚举&#xff08;Enum&am…

机器学习基础08-模型选择02-分类算法矩阵(基于Pima 数据集)

算法评估矩阵&#xff08;Algorithm Evaluation Metrics&#xff09;用于评估机器学习算法在特定任务上的性能。不同的任务可能会使用不同的评估矩阵&#xff0c;因为每个任务的优劣衡量标准都不同。 分类算法矩阵 分类问题或许是最常见的机器学习问题&#xff0c;并且有多种评…

【算法心得】C++map用不着map.find(arr[j])!=map.end();js的map是map不是哈希;编译器选GNU

https://leetcode.com/problems/count-of-range-sum/ https://vjudge.csgrandeur.cn/problem/CodeForces-459D 这两题都是线段树&#xff0c;很明显的单点修改区间查询 leetcode那题我觉得map用hashmap就行&#xff0c;但是好像js里没有hashmap&#xff0c;那就 Map() 也行吧…

vue、uniapp直传阿里云文档

前端实现文件上传到oss&#xff08;阿里云&#xff09;适用于vue、react、uni-app&#xff0c;获取视频第一帧图片 用户获取oss配置信息将文件上传到阿里云&#xff0c;保证了安全性和减轻服务器负担。一般文件资源很多直接上传到服务器会加重服务器负担此时可以选择上传到oss&…

SpringCloudAlibaba之Sentinel(一)流控篇

前言&#xff1a; 为什么使用Sentinel&#xff0c;这是一个高可用组件&#xff0c;为了使我们的微服务高可用而生 我们的服务会因为什么被打垮&#xff1f; 一&#xff0c;流量激增 缓存未预热&#xff0c;线程池被占满 &#xff0c;无法响应 二&#xff0c;被其他服务拖…

阿里云容器服务助力极氪荣获 FinOps 先锋实践者

作者&#xff1a;海迩 可信云评估是中国信息通信研究院下属的云计算服务和软件的专业评估体系&#xff0c;自 2013 年起历经十年发展&#xff0c;可信云服务评估体系已日臻成熟&#xff0c;成为政府支撑、行业规范、用户选型的重要参考。 2022 年 5 月国务院国资委制定印发《…

Linux 快速创建桌面图标

在安装 tar.gz 这类型压缩文件时&#xff0c;通常启动文件是.sh文件。文章主要记录快速添加到桌面图标。 1、解压 tar -zxvf XXX.tar.gz 2、创建桌面图标文件 touch XXX.desktop 3、文件中配置 [Desktop Entry] NameXXX CommentZZZ Exec/软件可执行文件所在目录/可执行文…

SpringBoot核心内容梳理

1.SpringBoot是什么? Spring Boot是一个基于Spring框架的快速开发应用程序的工具。它简化了Spring应用程序的创建和开发过程&#xff0c;使开发人员能够更快速地创建独立的、生产就绪的Spring应用程序。它采用了“约定优于配置”的原则&#xff0c;尽可能地减少开发人员需要进…

为代码生成一个良好可读的API文档-Doxygen简单实战

需求&#xff1f;为什么要有API文档 在代码开发过程中&#xff0c;我们会发现有这样的情况&#xff0c;其他团队的代码和自己团队的代码相异甚大&#xff0c;如果没有一个统一规范的文档来对接&#xff0c;会造成很多交流沟通上的不便&#xff0c;但我们又不想浪费时间去边写说…

C++STL——map/multimap容器详解

纵有疾风起&#xff0c;人生不言弃。本文篇幅较长&#xff0c;如有错误请不吝赐教&#xff0c;感谢支持。 &#x1f4ac;文章目录 一.对组&#xff08;pair&#xff09;二.map/multimap基本概念三.map容器常用操作①map构造函数②map迭代器获取③map赋值操作④map大小操作⑤map…

转录组下游分析 | 懒人分析推荐

写在前面 今天在GitHub看到一个博主写的RNASeqTool的ShinApp&#xff0c;里面包含了PCA、DESeq2、volcano、NormEnrich、GSEA、Gene tred analysis和WGCNA分析。使用后还是很方便的&#xff0c;就此推荐给大家。感兴趣可以自己操作即可。 GitHub网址 https://github.com/Cha…

C++、python双语言弹窗教程与对比

Messagebox弹窗 MessageBox指的是显示一个模态对话框&#xff0c;其中包含一个系统图标、 一组按钮和一个简短的特定于应用程序消息&#xff0c;如状态或错误的信息。消息框中返回一个整数值&#xff0c;该值指示用户单击了哪个按钮。 例子&#xff1a; 本文介绍了用C、Pytho…

flask服务生成证书文件,采用https访问,开启用户密码验证

openssl req -x509 -newkey rsa:4096 -nodes -out cert.pem -keyout key.pem -days 3072开启用户密码验证 auth.verify_password def verify_password(username, password):if username abcdefg and password 1234546:return usernameapp.route(/post_request, methods[POST…

使用Canvas裁剪图片

使用Canvas裁剪图片 概述 在Web开发中&#xff0c;我们经常需要对图片进行裁剪&#xff0c;以满足不同尺寸需求或者实现图片的局部展示。本篇博客将带您深入了解如何使用Canvas技术来实现图片的裁剪功能。我们将通过一个实例来演示如何利用Canvas绘制图片&#xff0c;并通过蒙…

【LeetCode】647. 回文子串

题目链接 文章目录 1. 思路讲解1.1 方法选择1.2 dp表的创建1.3 状态转移方程1.4 填表顺序 2. 代码实现 1. 思路讲解 1.1 方法选择 这道题我们采用动态规划的解法&#xff0c;倒不是动态规划的解法对于这道题有多好&#xff0c;它并不是最优解。但是&#xff0c;这道题的动态…

devops-发布vue前端项目

回到目录 将使用jenkinsk8s发布前端项目 1 环境准备 node环境 在部署jenkins的服务器上搭建node环境 node版本 # 1.拉取 https://nodejs.org/download/release/v20.4.0/node-v20.4.0-linux-x64.tar.gz# 2.解压到/usr/local目录下 sudo tar xf v20.4.0.tar.gz -C /usr/loc…

正则表达式的应用及示例解析

正则表达式&#xff08;Regular Expression&#xff0c;简称Regex&#xff09;是由特殊字符组成的模式字符串&#xff0c;用于匹配和搜索文本中的特定模式。它在数据处理、文本搜索和替换等方面广泛应用。本文将介绍正则表达式的基本语法&#xff0c;并提供常见的正则表达式示例…

关于数据权限的设计

在项目实际开发中我们不光要控制一个用户能访问哪些资源&#xff0c;还需要控制用户只能访问资源中的某部分数据。 控制一个用户能访问哪些资源我们有很成熟的权限管理模型即RBAC&#xff0c;但是控制用户只能访问某部分资源&#xff08;即我们常说的数据权限&#xff09;使用R…