内存组件及Nginx内存池的实现

本文从传统内存的弊端开始讲起,引出设置内存池的必要性,进而分析Nginx的内存池源码

1.C/C++传统内存操作的弊端

常用的内存操作函数

void *malloc(size_t size);
void *calloc(size_t nmemb,size_t size);
void *realloc(void *ptr,size_t size);
void free(void *ptr);

malloc	在内存的动态存储区中分配一块长度为size字节的连续区域返回该区域的首地址.
calloc	与malloc相似,参数size为申请地址的单位元素长度,nmemb为元素个数,即在内存中中请nmemb*size字节大小的连续地址空间.内存会初始化0
realloc	给一个已经分配了地址的指针重新分配空间,参数ptr为原有的空间地址,newsize是重新申请的地址长度.ptr若为NULL,它就等同于malloc.
    
======================================================================================  
int		brk(void *addr);
void 	*shrk(intptr_t	increment);

int		posix_memalign(void **memptr,size_t	alignment,size_t size); 
void 	*memalign(size_t alignment,size_t	size) ;
void 	*valloc(size_t size);

 
brk sbrk		改变进程堆区的终止处;
posix_memalign 	返回size 字节的动态内存,地址是alignment的倍数。alignment必须是2的幕和void指针大小的倍数;
memalign 		分配size字节的动态内存,地址是alignment的倍数的内存块。参数alignment必须是2的幕!
valloc			相当于使用页的大小作为对齐长度,使用memalign来分配内存

弊端一

高并发时较小内存块使用导致系统调用频繁,降低了系统的执行效率

内存组件及Nginx内存池的实现_第1张图片

弊端二

频繁使用时增加了内存的碎片,降低内存使用效率

内部碎片——已经被分配出去(能明确指出属于哪个进程)却不能被利用的内存空间

产生根源

  1. 内存分配必须起始于可被4,8或16整除(视处理器体系结构而定)的地址
  2. MMU的分页机制的限制

内存组件及Nginx内存池的实现_第2张图片

弊端三

没有垃圾回收机制,容易造成内存泄漏,导致内存枯竭

情形1

void log_error(char *reason) 
{ 
    char *p1;
    p1 = malloc(100);
    sprintf(p1,"The f1 error occurred because of '%s'.", reason); 
    log(p1);
}

情形2

int getkey(char *filename) 
{ 
    FILE *fp;
    int key;
    fp = fopen(filename, "r"); 
    fscanf(fp, "%d", &key);
    //fclose(fp);
    return key;
}

弊端四

内存分配与释放的逻辑在程序中相隔较远时,降低程序的稳定性

ret get_stu_info(Student* _stu	) 
{ 
    char* name= NULL;
	name = getName(_stu->no);
	//处理逻辑
    if(name) {
	    free(name); 		//这里free掉了一个栈内存,报错!!!
       	key = NULL;
	}
}
 
	char stu_name[MAX];
	char * getName(int stu_no){
		//查找相应的学号并赋值给 stu_name 
    	snprintf(stu_name,MAX,%s”,name);
		return stu_name;
	}

2.弊端如何解决

内存管理维度分析

内存组件及Nginx内存池的实现_第3张图片

内存管理组件选型

PtMalloc (glibc自带) TcMalloc JeMalloc
概念 Glibc自带 Google开源 Jason Evans(FreeBSD开发人员)
性能 (一次malloc/free 操作) 300ns 50ns <=50ns
弊端 锁机制降低性能,容易导致内存碎片 1%左右的额外内存开销 2%左右的额外内存开销
优点 传统、稳定 线程本地缓存,多线程分配效率高 线程本地缓存,多核多线程分配效率相当高
使用方式 Glibc编译 动态链接库 动态链接库
谁在用 较普遍 safari、chrome等 facebook、firefox等
适用场景 除特别追求高效内存分配以外的 多线程下高效内存分配 多线程下高效内存分配

3.内存池技术

什么是内存池技术?

在真正使用内存之前,先申请一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需要时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存,同一对程序所使用的内存进行同一的分配和回收。这样做的一个显著优点是,使得内存分配效率得以很大的提升

内存组件及Nginx内存池的实现_第4张图片

内存池如何解决弊端?

  • 高并发时系统调用频繁,降低了系统的执行效率

内存池提前预先分配大块内存,统一释放,极大的减少了 malloc 和 free 等函数的调用

  • 频繁使用增加了系统内存的碎片,避免了碎片的产生

内存池每次请求分配大小适度的内存块,避免了碎片的产生

  • 没有垃圾回收机制,容易造成内存泄漏

在生命周期结束后统一释放内存,完全避免了内存泄露的产生

  • 内存分配与释放的逻辑在程序中相隔较远时,降低程序的稳定性

在生命周期结束后统一释放内存,避免重复释放指针或释放空指针等情况

4.高性能内存池设计与实现

设计思路(分而治之)

内存组件及Nginx内存池的实现_第5张图片

高性能内存池结构图

内存组件及Nginx内存池的实现_第6张图片

关键数据结构

typedef struct {
	u_char	*last;	II 保存当前数 据块中内存分配指针的当前位 置
	u_char	*end;	II 保存内存块的结束位置
	ngx_pool_t	*next;	II 内存池由多块内存块组成, 指向下一个数据块的位置
	ngx_uint_t	failed;	II 当前数 据块内存不足引起分 配失败的次数
}ngx_pool_data_t;


struct ngx_pool_s {
	ngx_pool_data_t	 d;	II 内存池当前的 数据区指针的结构体
	size_t			max;	// 当前数 据块最大可分配的内存大小 C Bytes) 
    ngx_pool_t		*current;	//当前正在使用的数据块的指针
	ngx_pool_large_t *large;	// pool 中指向大数据块的指针(大数据快是指 size > max 的数据块)
}

ngx_pool_t结构示意图(大小为1024的池)

内存组件及Nginx内存池的实现_第7张图片

Nginx内存池基本操作

内存池创建、销毁和重置

操作 函数
创建内存池 ngx_pool_t *ngx_create_pool(size_t size);
销毁内存池 void ngx_destory_pool(ngx_pool_t *pool);
重置内存池 void ngx_reset_pool(ngx_pool_t *pool);

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

操作 函数
内存申请(对齐) void *ngx_palloc(ngx_pool_t *pool,size_t size);
内存申请(不对齐) void *ngx_pnalloc(ngx_pool_t *pool,size_t size);
内存申请(对齐并初始化) void *ngx_pcalloc(ngx_pool_t *pool,size_t size);
内存清楚 ngx_int_t *ngx_free(ngx_pool_t *pool,void *p);

你可能感兴趣的:(高并发,并发,内存池,C/C++,Nginx)