c++内存池的设计和实现,c语言内存池 开源
传统方法在高并发内存池设计中的弊端在传统C语言中,我们使用malloc、calloc、realloc和free来申请内存分配和释放。该函数的原型如下。c是新的,删除。
void * malloc(size _ t size);Malloc在内存的动态存储区中分配了一个大小为字节的连续区域,并返回该区域的第一个地址。void *calloc(size_t nmemb,size _ t size);类似于malloc,参数size是应用地址的单位元素长度,nmeb是元素个数,即如果应用是在内存中做的,那么在一个连续的nmemb * size字节的地址空间中,内存会被初始化为0。void *realloc(void *ptr,size _ t size);将空间分配给已经分配了地址的指针。参数ptr是原来的空间地址,大小是重新申请的地址的长度。如果为NULL,则相当于malloc。void free(void * ptr);虐待
缺点:高并发使用较小的内存块,导致系统调用频繁,降低了系统的执行效率。
缺点:频繁使用增加了系统内存的碎片,降低了内存使用效率。内存碎片(memory fragmentation)——已经分配(可以明确指出是哪个进程)但无法利用的内存空间。起源:内存分配必须从可被4、8和16整除的地址开始(取决于处理器架构)。MMU的分页机制是有限的,操作系统是按页给空间的。缺点:没有垃圾收集机制,容易造成内存泄漏,导致内存耗尽。示例:malloc后面没有free void log _ error(char * reason){
char * p1
P1=malloc(100);
sprintf(p1,“由于“%s”,出现了f1错误。”,原因);
日志(P1);
}
例2:打开一个文件还需要申请内存资源。fopen之后没有fclose。int getkey(char *filename) {
FILE * fp
int键;
fp=fopen(文件名, r );
fscanf(fp, %d ,key);
//fclose(FP);
回车键;
}
缺点:当程序中内存分配和释放的逻辑相距甚远时,降低了程序的稳定性。比如由于距离太远,导致非malloc的变量被误释放。
针对弊端,选择系统层——内存管理组件的解决方案:使用高性能内存管理组件Tcmalloc和Jemalloc代替Glibc Ptmalloc,解决优化效率和碎片问题。内存管理组件的选择:
应用层——内存池技术什么是内存池技术?即在实际使用内存之前,先申请分配一定数量(一般情况下)大小相等的内存块备用。当有新的内存需求时,一些内存块从内存池中分离出来。如果剩余内存块不够,我们会继续申请新的内存,统一分配和回收程序使用的内存。这样做的一个显著优点是大大提高了内存分配的效率。个人理解:内存池有什么用?减少频繁的系统调用以减少时间开销,一次性申请一个大内存,然后分配给需要的程序进程。如果不够,再要。
内存如何解决缺点?当并发度高时,系统调用频繁,降低了系统的执行效率。内存池提前分配大块内存,统一释放,大大减少了malloc和free函数的调用。频繁使用会增加系统内存碎片,降低内存使用效率。每次池请求分配中等大小的内存块时,都会避免碎片。没有垃圾收集机制,很容易造成内存泄漏。在声明期结束后统一释放内存,完全避免了内存泄漏的发生。当程序中内存分配和释放的逻辑相距甚远时,就会降低程序的稳定性。声明期结束后,统一释放内存,避免重复释放指针或释放空指针。如何实现高并发内存池?高并发——是互联网分布式系统架构设计必须考虑的因素之一。这通常意味着系统通过设计可以同时并行处理许多请求。高并发的特点:响应时间短、吞吐量大、每秒QPS并发用户数高、内存池设计考虑事项多。设计逻辑要尽可能简单,避免不同请求之间的交互,尽量减少不同模块之间的耦合。内存池的生存期应该尽可能短,与请求或连接的周期相同,以减少碎片堆积和内存泄漏。想法——分而治之。对于每个请求或连接,将建立一个相应的内存池。内存池建立后,我们可以直接从内存池中申请需要的内存,不考虑内存释放,内存池一旦使用就销毁。区分大小内存块的申请和释放。如果内存块大于池大小,则定义为大内存块,存储在单独的大内存块链表中,并立即分配释放。小于池大小的内存块定义为小内存块,直接从预分配的内存块中提取。如果不够,那么池中的内存会被扩大,小内存块在生命周期内不会被释放,直到最后被销毁。Nginx内存池结构设计主要结构图:
更详细:详见一些源代码分析。
NG_ POOL _ T(内存池的头节点)的结构示意图,其中没有显示链接在large后面的大内存块。
部分源代码分析注:部分Nginx源代码可能被删除。这里只分析一些关于内存池的内容。在下面的文字中,小内存是一般内存。在nginx内存池的过程中,首先,创建一个头节点。头节点不同于其他常见的内存块。除了要分配的内存区域,它还存储当前内存池的其他相关信息。后续内存不够,无法扩展。扩展内存块链接到内存池的头节点后,扩展内存块分为大内存块和通用内存块,分别链接到不同的位置,大内存链表和小内存链表。申请内存时自动扩展不够。申请内存时,先判断申请大小。如果超过当前内存池设置的最大应用大小,申请大内存,否则申请小内存。申请大内存时,先申请需要的空间,然后在内存池头部的大内存节点中寻找有没有可重用的节点,如果有,就链接新创建的内存。反之,创建一个新的节点,链接创建的内存,插入到大内存链表中。申请小内存时,先寻找头节点后面链接的其他小内存块,看是否有可用空间的小内存块。如果用了,如果没用,再申请,也链接到内存池头节点的小内存块链表。重置内存池,释放内存池中的大块内存,然后重置每个小块内存。主数据结构:
内存头结构:ngx_pool_s struct ngx_pool_s {
ngx _ pool _ data _ t d;//内存池当前数据区指针的结构
size _ t max//当前数据块的最大可分配内存大小(字节)
ngx _ pool _ t *当前;//指向当前正在使用的数据块的指针
ngx _ pool _ large _ t * large//指向池中大数据块的指针(
u _ char * last//保存当前数据块中内存分配指针的当前位置。每次Nginx程序从内存池中申请内存时,从该指针保存的位置开始划分出请求的内存大小,并更新该指针到新的位置。
u _ char * end//保存内存块的结束位置
ngx _ pool _ t * next//内存池由多块内存块组成,指向下一个数据块的位置。
ngx_uint_t失败;//当前数据块内存不足引起分配失败的次数
} ngx _ pool _ data _ t;
大内存块的结构体:ngx_pool_large_s结构ngx _池_大_s
ngx _ pool _ large _ t * next//指向下一块大内存块的指针
void * alloc//大内存块的起始地址
};
//别名
typedef struct ngx _ pool _ large _ s ngx _ pool _ large _ t;
创建内存池:ngx _创建_池ngx _池_t *
ngx _创建_池(大小_t大小)
{
ngx _ pool _ t * p;
//开辟空间
p=NGX _ memalign(NGX _ POOL _ ALIGNMENT,size);
if (p==NULL) {
返回空
}
警察。last=(u _ char *)p sizeof(ngx _ pool _ t);//指向实际要分配内存的位置,也就是跳过了前面那些
警察。end=(u _ char *)p size;//指向末端
警察。next=NULL
警察。失败=0;
size=size-sizeof(ngx _ pool _ t);
p-MAX=(size NGX _ MAX _ ALLOC _ FROM _ POOL)?size:NGX _ MAX _ ALLOC _ FROM _ POOL;//检测剩余空间
p-电流=p;//正在工作(分配)的内存块
p-large=NULL;
返回p;
}
其中负责开辟空间的函数ngx_memalign,其实就是将分配内存进行了简单的封装。这里为了省事,将Nginx源代码简化。//将Nginx源代码简化
#define ngx_memalign(alignment,size) ngx_alloc(size)
//开辟空间,将分配内存进行简单的封装。
无效*
ngx_alloc(size_t size)
{
void * p;
p=malloc(大小);
if (p==NULL) {
fprintf(stderr, malloc(%zu失败,大小);
}
if(debug) fprintf(stderr, malloc: %p:%zu ,p,size);
返回p;
}
从内存池中申请空间:ngx_palloc void *
ngx_palloc(ngx_pool_t *pool,size_t size)
{
#如果!(NGX_DEBUG_PALLOC)
if (size=pool- max) {
返回ngx_palloc_small(池,大小,1);//如果要分配小内存块-从池子中取
}
#endif
返回ngx_palloc_large(池,大小);//如果要分配大内存块-扩展
}
申请小内存空间:ngx_palloc_small //从内存池头结点中所挂的一块一块普通内存块中搜索看是否有容量够的。
静态内嵌无效*
ngx _ palloc _ small(ngx _ pool _ t * pool,size_t size,ngx_uint_t align){
u _ char * m;
ngx _ pool _ t * p;
p=池-电流;
做{
m=p-d。最后;
if (align){//是否边界对齐
//对齐到为是对齐数的倍数
m=ngx_align_ptr(m,NGX _ ALIGNMENT);
}
//剩余内存是否够用
if ((size_t) (p- d.end - m)=size) {
p- d最后=m大小;
返回m;
}
p=p-d。接下来;//切换到下一块进行剩余容量判断
} while(p);
//实在不行了,重新创建内存块。
返回ngx_palloc_block(池,大小);
}
创建普通内存块:ngx_palloc_block静态无效*
ngx _ palloc _ block(ngx _ pool _ t * pool,size_t size){
u _ char * m;
尺寸大小
ngx_pool_t *p,*新
psize=(size _ t)(pool-d . end-(u _ char *)pool);
m=NGX _ memalign(NGX _ POOL _ ALIGNMENT,psize);
if (m==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大小;
//将内存池中链接的所有结点都遍历一遍,将它们的尝试次数1,大于四的直接淘汰。
for (p=池-电流;p-d .下一个;p=p- d.next) {
如果(p- d失败4) {
池电流=p-d。接下来;//4淘汰掉
}
}
警察。next=new
返回m;
}
申请大空间(大内存块):ngx _ palloc _大型静态无效*
ngx _ palloc _ large(ngx _ pool _ t * pool,size_t size){
void * p;
ngx _ uint _ t n;
ngx _ pool _ large _ t *大型
p=ngx_alloc(大小);//申请一块新的够用大空间
if (p==NULL) {
返回空
}
n=0;
//先找有没有之前不用的闲置大内存。
对于(大=池-大;大;large=large- next) {//遍历池子的大内存链表
if (large- alloc==NULL) {
large-alloc=p;
返回p;
}
if(n ^ 3){//就尝试找四次
打破;
}
}
//创建一个大内存结点
large=ngx_palloc_small(pool,sizeof(ngx_pool_large_t),1);//第一次肯定会直接执行这个
if (large==NULL) {
ngx _ free(p);
返回空
}
//类似链表头插法
large-alloc=p;
large-next=pool-large;
大型池=大;
返回p;
}
销毁内存池:ngx_destroy_pool //销毁内存池
空的
ngx _销毁_池(ngx _池_ t *池)
{
ngx_pool_t *p,* n;
ngx _ pool _ large _ t * l;
#if (NGX_DEBUG)
for (l=池-大;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,空闲:%p,未使用:%zu ,p,p-d . end-p-d . last);
if (n==NULL) {
打破;
}
}
#endif
//真正的销毁处理流程
for (l=池-大;l;l=l- next) {//处理大块
如果(左分配){
ngx _ free(l-alloc);
}
}
//小块
for (p=pool,n=pool-d . next;/* void */;p=n,n=n- d.next) {
ngx _ free(p);
if (n==NULL) {
打破;
}
}
}
清理大内存块:ngx_pfree ngx_int_t
ngx_pfree(ngx_pool_t *pool,void *p)
{
ngx _ pool _ large _ t * l;
//遍历寻找目标-大内存块p
for (l=池-大;l;l=l- next) {
if (p==l- alloc) {
fprintf(stderr, free: %p ,l-alloc);
ngx _ free(l-alloc);
l-alloc=NULL;
返回NGX _ OK
}
}
返回NGX _拒绝
}
清理内存块:ngx_free //ngx_free为自由的的别名
#定义ngx_free免费
重置内存池:ngx _重置_池无效
ngx _重置_池(ngx _池_ t *池)
{
ngx _ pool _ t * p;
ngx _ pool _ large _ t * l;
//释放大块
for (l=池-大;l;l=l- next) {
如果(左分配){
ngx _ free(l-alloc);
}
}
//重置每个小块
for(p=pool;p;p=p- d.next) {
警察。last=(u _ char *)p sizeof(ngx _ pool _ t);
警察。失败=0;
}
池电流=池;
//pool-chain=NULL;
pool-large=NULL;
}
示例:#include mem_core.h
#define BLOCK_SIZE 16 //每次分配内存块大小
#定义查看内存状况池大小(1024 * 4) //内存池每块大小
int main(int argc,char **argv){
int i=0,k=0;
int use _ free=0;
ngx _ pagesize=get pagesize();
if(argc=2){//不使用线程池
use _ free=1;
printf( use malloc/free \ n );
} else {//使用线程池
printf(使用内存池. \ n );
}
如果(!免费使用){
char * ptr=NULL
for(k=0;k 1024 * 500k ){
ngx _ POOL _ t * mem _ POOL=ngx _ create _ POOL(MEM)池_大小);//创建
for(I=0;i 1024i ){
ptr=ngx_palloc(mem_pool,BLOCK _ SIZE);//申请
如果(!ptr) fprintf(stderr, ngx_palloc失败. \ n );
else {//模拟使用
* ptr= \ 0
*(ptr BLOCK _ SIZE-1)= \ 0 ;
}
}
ngx _销毁池(内存_池);
}
}否则{
char * ptr[1024];
for(k=0;k 1024 * 500k ){
for(I=0;i 1024i ){
ptr[I]=malloc(BLOCK _ SIZE);
如果(!ptr[i]) fprintf(stderr, malloc失败。原因:%s\n ,strerror(errno));
否则{
* ptr[I]= \ 0 ;
*(ptr[I]BLOCK _ SIZE-1)= \ 0 ;
}
}
for(I=0;i 1024i ){
if(ptr[I])free(ptr[I]);
}
}
}
返回0;
}
对比:
补充在C语言中空的类型的指针可以直接赋值给其它指针类型,但是在C中不行,必须将无效*强制转换为对应类型再赋值。例如:malloc默认返回void*,这允许用于分配任何类型C语言中的分配内存函数的返回值就是一个无效*型指针,我们可以把它直接赋给一个其他类型的指针,但从安全的编程风格角度以及兼容性上讲,最好还是将返回的指针强制转换为所需的类型。参考文章:C语言指针的初始化和赋值C #中的空指针在C语言风格的字符串中,手动添加一个\0,用打印函数打印输出时会截至到第一个\0,也就是遇到\0停止,但是实际的大小并不会改变。如下代码所示:#include stdio.h
int main(void){
char c[]= 123 ;
printf(%d\n ,sizeof(c));//4
c[1]= \ 0 ;
printf(%d\n ,sizeof(c));//4
printf(%s\n ,c);//1
返回0;
}
转载请联系作者取得转载授权,否则将追究法律责任。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。