高性能内存池设计【Nginx高性能内存池源码分析】

文章目录

  • 高性能内存池Nginx内存池源码分析
    • 应用内存池源码
    • 其他的一些简单方法
    • 运行效果

高性能内存池Nginx内存池源码分析

对于内存池不是很了解的小伙伴可以先看这两篇文章
1.内存池框架
2.传统内存管理的弊端和解决方案

高性能内存池设计【Nginx高性能内存池源码分析】_第1张图片


应用内存池源码

在这里写了个main方法来调用内存池的各个方法,运行的场景是动态分配500*1024*1024次内存,对比正常使用c语言的malloc和使用内存池进行内存分配的时间,看main方法。

#include "mem_core.h"
#define BLOCK_SIZE  16   		 //每次分配内存块大小

#define MEM_POOL_SIZE (1024 * 4) //内存池每块大小

int main(int argc, char **argv)
{
	int i = 0, k = 0;
	int use_free = 0;
	
	//获得页的的大小
	ngx_pagesize = getpagesize();

	//如果参数数目≥2则使用传统方法
	if(argc >= 2){
		use_free = 1;
		printf("use malloc/free\n");		
	} 
	//否则使用内存池
	else {
        printf("use mempool.\n");
    }

	if(!use_free){
	    char * ptr = NULL;
	
	    for(k = 0; k< 1024 * 500; k++)
	    {
			//创建内存池
	        ngx_pool_t * mem_pool = ngx_create_pool(MEM_POOL_SIZE);
		    
			for(i = 0; i < 1024 ; i++)
			{
				//在内存池中分配内存
				ptr = ngx_palloc(mem_pool,BLOCK_SIZE);

				if(!ptr) fprintf(stderr,"ngx_palloc failed. \n");
				else {
					*ptr = '\0';
					*(ptr + BLOCK_SIZE -1) = '\0';
				}
			}
		    //销毁内存池
            ngx_destroy_pool(mem_pool);
	    }
	} else {
	    char * ptr[1024];
	    for(k = 0; k< 1024 * 500; k++){
			for(i = 0; i < 1024 ; i++)
			{
				ptr[i] = malloc(BLOCK_SIZE);
				if(!ptr[i]) fprintf(stderr,"malloc failed. reason:%s\n",strerror(errno));
				else{
					*ptr[i] = '\0';
					*(ptr[i] +  BLOCK_SIZE - 1) = '\0';
				}
			}
			//释放刚刚分配的内存
			for(i = 0; i < 1024 ; i++){
				if(ptr[i]) free(ptr[i]);
			}
	    }

	}
	return 0;
}

再来回顾一下内存池的几个基本操作:
1.内存池创建、销毁和重置:

高性能内存池设计【Nginx高性能内存池源码分析】_第2张图片

2.内存池申请、释放和回收操作:

高性能内存池设计【Nginx高性能内存池源码分析】_第3张图片

现在让我们来看看刚刚main方法的几个方法吧:
1、ngx_pool_t * mem_pool = ngx_create_pool(MEM_POOL_SIZE);//创建内存池,传入参数内存池块的大小
2、ptr = ngx_palloc(mem_pool,BLOCK_SIZE);//在mem_pool内存池中分配个BLOCK_SIZE大小的内存
3、ngx_destroy_pool(mem_pool);//销毁内存池


ngx_create_pool(MEM_POOL_SIZE);创建内存池

这个方法比较简单,就不单独解释了,解释都在注释里面了

//指定池子的大小单位为Byte
ngx_pool_t *
ngx_create_pool(size_t size)
{
    //第一块,大块的
    ngx_pool_t  *p;

    //申请内存,采用内存对齐的方法申请内存
    p = ngx_memalign(NGX_POOL_ALIGNMENT, size);
    if (p == NULL) {
        return NULL;
    }
	//将可分配空间的开始指针,移动到ngx_pool_t结构体以后
    p->d.last = (u_char *) p + sizeof(ngx_pool_t);
    //将可分配空间的尾巴指针,移动到刚刚分配的内存块底部
    p->d.end = (u_char *) p + size;
    //刚刚创建没有下一个内存块
    p->d.next = NULL;
    //失败的次数为0
    p->d.failed = 0;
	//这里是将分配的内存块大小-第一个块的结构体的大小,目的是在下一个语句中更新max的值
    size = size - sizeof(ngx_pool_t);
    p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;

    p->current = p;
    //p->chain = NULL;
    p->large = NULL;
	
	//将线程池地址返回
    return p;
}

ngx_palloc(mem_pool,BLOCK_SIZE);在内存池mem_pool中分配内存

void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
    //如果要分配的内存大小小于max大小那么就使用ngx_palloc_small方法
    if (size <= pool->max) {
        return ngx_palloc_small(pool, size, 1);
    }
#endif
	//否则就是以大内存进行处理(链表,想起来了吗?)
    return ngx_palloc_large(pool, size);
}

1、看看ngx_palloc_small(pool, size, 1);分配小内存

static inline void *
ngx_palloc_small(ngx_pool_t *pool, size_t size, ngx_uint_t align)
{
    u_char      *m;
    ngx_pool_t  *p;
    //指向当前使用的数据内存块
    p = pool->current;

    do {
        //
        m = p->d.last;
		//是否要使用内存对齐
        if (align) {
            //这个语句就是进行内存对齐,如果m指向63,而NGX_ALIGNMENT为8,那么m会被重新选位置为64,如果m原来指向65,那么m会被重新选位置为72
            m = ngx_align_ptr(m, NGX_ALIGNMENT);
        }

        //看位置够不够我们使用
        //如果够我们使用
        if ((size_t) (p->d.end - m) >= size) {
            p->d.last = m + size;

            return m;
        }

        //如果不够分配则向下一个内存块找
        p = p->d.next;

    } while (p);

    //重新创建一个内存块
    return ngx_palloc_block(pool, size);
}

流程描述:
①将指针指向第一个块中的current指针指向的内存块
②从①中指定内存块开始看有没有空闲的空间来分配size大小的内存(这里要看是否采用对齐策略【可以看看上面的注释哟】)
③如果当前内存块中的空闲空间不够,那么就继续往后面的内存块找可以分配内存的内存块
④找到可以分配的内存块的话,将该内存块的last指针进行更新,新的last位置是原来的last位置+size(在没有对其的情况下),这样就将这部分的内存分配给出去了,返回分配时的起始地址
④如果现在分配的内存块都不够来分配size大小的内存,就新添一个内存块


2、看看ngx_palloc_large(pool, size);分配大内存

static void *
ngx_palloc_large(ngx_pool_t *pool, size_t size)
{
    void              *p;
    ngx_uint_t         n;
    ngx_pool_large_t  *large;
    p = ngx_alloc(size);
    if (p == NULL) {
        return NULL;
    }
    n = 0;
    //如果有之前释放的大内存块,则它的large->alloc是为NULL,重用节点
    for (large = pool->large; large; large = large->next) {
        if (large->alloc == NULL) {
            large->alloc = p;
            return p;
        }
        //如果找重用节点≥3次就不找了
        if (n++ > 3) {
            break;
        }
    }
    
    //在内存块中分配一个ngx_pool_large_t结构体【指针指向这里,然后这里指向大内存块真实存储位置】
    large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
    if (large == NULL) {
        ngx_free(p);
        return NULL;
    }
    large->alloc = p;
    large->next = pool->large;
    pool->large = large;
    return p;
}

流程描述:
①分配一个size大小的内存
②找之前释放的大内存块,因为是标记删除,而不是真实删除,这些节点是可以直接重用的【但是,如果从链表的第一个large开始往后找超过3个的话那么就不找可重用节点了,就自己重新分配一个新的节点】,找到的话就直接return刚刚创建的大内存块的地址
③如果找不到可重用节点就重新分配一个节点用来存储大内存块的位置和各种信息
④采用头插法将刚刚创建的large节点接入到大内存块链表中


ngx_destroy_pool(mem_pool);销毁内存池,资源回收

void
ngx_destroy_pool(ngx_pool_t *pool)
{
    ngx_pool_t          *p, *n;
    ngx_pool_large_t    *l;
    //ngx_pool_cleanup_t  *c;
    /*for (c = pool->cleanup; c; c = c->next) {
        if (c->handler) {
            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
                           "run cleanup: %p", c);
            c->handler(c->data);
        }
    }*/
#if (NGX_DEBUG)
    /*
     * we could allocate the pool->log from this pool
     * so we cannot use this log while free()ing the pool
     */
    for (l = pool->large; l; l = l->next) {
        fprintf(stderr,"free: %p", l->alloc);
    }
    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
        fprintf(stderr,"free: %p, unused: %zu", p, p->d.end - p->d.last);
        if (n == NULL) {
            break;
        }
    }
#endif
    //释放大内存块
    for (l = pool->large; l; l = l->next) {
        if (l->alloc) {
            ngx_free(l->alloc);
        }
    }
    //销毁池子中一块一块的内存块
    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
        ngx_free(p);
        if (n == NULL) {
            break;
        }
    }
}

流程描述:
①通过大内存块的链表将大内存块一个一个进行释放
②将内存池的块进行统一释放【注意:那些小的内存块不能够单独释放只能通过这个方法进行统一全部释放,而大内存块是可以使用ngx_pfree进行单独释放的


ngx_palloc_block(ngx_pool_t *pool, size_t size)在内存池pool中追加一个内存块,并分配内存大小为size的内存

static void *
ngx_palloc_block(ngx_pool_t *pool, size_t size)
{
    u_char      *m;
    size_t       psize;
    ngx_pool_t  *p, *new;
    psize = (size_t) (pool->d.end - (u_char *) pool);
    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize);
    if (m == NULL) {
        return NULL;
    }
    new = (ngx_pool_t *) m;
    new->d.end = m + psize;
    new->d.next = NULL;
    new->d.failed = 0;
    m += sizeof(ngx_pool_data_t);
    m = ngx_align_ptr(m, NGX_ALIGNMENT);
    new->d.last = m + size;
    for (p = pool->current; p->d.next; p = p->d.next) {
        if (p->d.failed++ > 4) {
            pool->current = p->d.next;
        }
    }
    p->d.next = new;
    return m;
}

流程描述:
①创建一个内存块【大小由自己刚开始设置的一样】,用m来指向
②更新end,next以及failed的值
③将m指针的位置发生偏移【从原来的位置偏移ngx_pool_data_t个大小】
④从m指向的位置分配一个大小为size的内存(参数传进来的),并更新last指针
⑤因为这里已经重新创建了一个内存块说明size大小的内存,原本创建的内存块不能提供这么大的内存,所以前面内存块失败次数都应该++,根据规则更新current指针。
⑥将刚刚创建的内存块和原本的内存块进行链接


其他的一些简单方法

释放内存池pool中管理的地址为p的大内存块

ngx_int_t
ngx_pfree(ngx_pool_t *pool, void *p)
{
    ngx_pool_large_t  *l;
    for (l = pool->large; l; l = l->next) {
        if (p == l->alloc) {
            fprintf(stderr,"free: %p", l->alloc);
            ngx_free(l->alloc);
            l->alloc = NULL;
            return NGX_OK;
        }
    }
    return NGX_DECLINED;
}

运行效果

高性能内存池设计【Nginx高性能内存池源码分析】_第4张图片
可以看到使用内存池完成相同的操作速度会快的多,所以在需要经常分配动态存储空间的程序中使用内存池可以很好的提升程序的运行速度,感谢各位能够看到这,如果对文章有不同见解或者有疑惑的同学请一定一定要在评论区中提出,谢谢!!
高性能内存池设计【Nginx高性能内存池源码分析】_第5张图片

你可能感兴趣的:(Linux,nginx,c++,c语言,算法)