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

news2024/9/25 11:16:35

跟着这篇文章学习:

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
    栈内是比堆内存快
*/

运行效果: 

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

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

相关文章

TikTok马来西亚站变动,指定物流服务商!

8月2日&#xff0c;据TechinAsia报道&#xff0c;TikTok已将百世快递在马来西亚的子公司BestExpressMalaysia&#xff0c;指定为其在马来西亚的物流服务商。目前&#xff0c;百世快递已在越南、泰国与TikTok展开类似合作。 合作后&#xff0c;百世马来子公司将为TikTokShop卖家…

js去除字符串中特殊字符并将数据转为数组格式

let str今-天-空-气-很-清-新;let liststr.split(-);console.log(list)使用split将特殊字符去除并将数据转为数组

解决vite+vue3项目npm装包失败

报错如下&#xff1a; Failed to remove some directories [ npm WARN cleanup [ npm WARN cleanup D:\\V3Work\\v3project\\node_modules\\vue, npm WARN cleanup [Error: EPERM: operation not permitted, rmdir D:\V3Work\v3project\node_modules\vue\reactivity\…

Spring @Scheduled单线程单实例的坑

文章目录 前言背景验证解决方案 前言 在 Java Spring 项目中经常会用 Scheduled 来实现一些定时任务的场景&#xff0c;有必要了解一些它使用时的问题和内部实现机制。本文是偶然间发现的一个问题&#xff0c;刷新了我的认知&#xff0c;分享给大家。 其他相关文章&#xff1…

Pytorch深度学习-----神经网络之Sequential的详细使用及实战详解

系列文章目录 PyTorch深度学习——Anaconda和PyTorch安装 Pytorch深度学习-----数据模块Dataset类 Pytorch深度学习------TensorBoard的使用 Pytorch深度学习------Torchvision中Transforms的使用&#xff08;ToTensor&#xff0c;Normalize&#xff0c;Resize &#xff0c;Co…

产品经理之路:从C端到B端的崭新旅程

理解C端和B端 在我们开始探讨从C端产品经理到B端产品经理的转变之前&#xff0c;我们首先需要理解C端和B端是什么&#xff0c;以及它们之间有什么区别。 C端产品与B端产品的基本区别 C端产品&#xff08;Consumer端产品&#xff09;是面向普通消费者的产品&#xff0c;如社交…

大数据课程F4——HIve的其他操作

文章作者邮箱&#xff1a;yugongshiyesina.cn 地址&#xff1a;广东惠州 ▲ 本章节目的 ⚪ 掌握HIve的join&#xff1b; ⚪ 掌握HIve的查询和排序 ⚪ 掌握HIve的beeline ⚪ 掌握HIve的文件格式 ⚪ 掌握HIve的基本架构 ⚪ 掌握HIve的优化&#xff1b; 一、jo…

一个对象的内存图,两个对象使用同一个方法的内存图

一个对象的内存图 public class Phone {String brand; //品牌double price; //价格String color; //颜色public void call(String who) {System.out.println("给" who "打电话");}public void sendMessage() {System.out.println("群发短信");…

【第一阶段】kotlin的range表达式

range:范围&#xff1a;从哪里到哪里的意思 in:表示在 !in&#xff1a;表示不在 … :表示range表达式 代码示例&#xff1a; fun main() {var num:Int20if(num in 0..9){println("差劲")}else if(num in 10..59){println("不及格")}else if(num in 60..89…

2023年自动化测试已成为标配?一篇彻底打通自动化测试...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 首先我们从招聘岗…

CompletableFuture原理与实践

文章目录 1 为何需要并行加载2 并行加载的实现方式2.1 同步模型2.2 NIO异步模型2.3 为什么会选择CompletableFuture&#xff1f; 3 CompletableFuture使用与原理3.1 CompletableFuture的背景和定义3.1.1 CompletableFuture解决的问题3.1.2 CompletableFuture的定义 3.2 Complet…

Shell脚本学习-for循环结构1

for循环语句主要用于执行次数有限的循环&#xff0c;而不是用于守护进程和无限循环。 语法结构&#xff1a; for 变量名 in 变量取值列表 do指令... done 提示&#xff1a;in 变量值列表 可以省略&#xff0c;省略就相当于 in "$"&#xff0c;也就是使用for i in &…

企业大数据可视化案例专题分享-入门

一、什么是数据可视化&#xff1f; 基本概念&#xff1a;数据可视化是以图示或图形格式表示的数据。让决策者可以看到以直观方式呈现的分析&#xff0c;以便他们可以掌握困难的概念或识别新的模式。借助交互式可视化&#xff0c;可以使用技术深入挖掘图表和图形以获取更多详细…

Java版工程项目管理系统平台+企业工程系统源码+助力工程企业实现数字化管理 em

鸿鹄工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离构建工程项目管理系统 1. 项目背景 一、随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大。为了提高工程管理效率、减轻劳动强度、提高信息处理速度和准确性&#xff0c;公司对内部工程…

IO进程线程第六天(8.3)线程,线程函数+XMind

创建两个线程&#xff1a;其中一个线程拷贝前半部分&#xff0c;另一个线程拷贝后半部分。 只允许开一份资源&#xff0c;且用互斥锁方式实现。 #include<stdio.h> #include<head.h> #include<pthread.h> struct file {int fp; int fq; off_t size; }; pth…

什么?你还没有用过JPA Buddy,那么你工作肯定没5年

1. 概述 JPA Buddy是一个广泛使用的IntelliJ IDEA插件&#xff0c;面向使用JPA数据模型和相关技术&#xff08;如Spring DataJPA&#xff0c;DB版本控制工具&#xff08;Flyway&#xff0c;Liquibase&#xff09;&#xff0c;MapStruct等&#xff09;的新手和有经验的开发人员…

投资圈爆火的文心杯创业大赛,大模型三大趋势值得关注!

“百模大战”打响&#xff0c;掀起大模型领域“创业热潮”。今年5月31日&#xff0c;百度启动“文心杯”创业大赛&#xff08;后简称“大赛”&#xff09;&#xff0c;不到1个月报名时间&#xff0c;吸引近1000个项目激烈角逐&#xff0c;在知名投资人和AI专家的权威评审和层层…

8 网络通信(下)

文章目录 wireshark 抓包工具的快速入门TCP/IP 协议详解TCP/IP 协议概述部分协议补充 OSI 参考模型及 TCP/IP 参考模型以太网帧格式ARP 数据报格式IP 段格式ICMP 协议TCP协议UDP 协议MTU和MSS补充 产品模式【C/S B/S】C/S 模式B/S 模式 DDOS简单介绍&#xff08;SYN Flooding&a…

【暑期每日一练】 day13

目录 选择题 &#xff08;1&#xff09; 解析&#xff1a; &#xff08;2&#xff09; 解析&#xff1a; &#xff08;3&#xff09; 解析&#xff1a; &#xff08;4&#xff09; 解析&#xff1a; &#xff08;5&#xff09; 解析&#xff1a; 编程题 题一…

Spring Cloud Feign+Ribbon的超时机制

在一个项目中&#xff08;数据产品&#xff09;&#xff0c;需要对接企业微信中第三方应用。在使用 Feign 的去调用微服务的用户模块用微信的 code 获取 access_token 以及用户工厂信息时出现 Feign 重试超时报错的情况&#xff0c;通过此篇文章记录问题解决的过程。 一、问题重…