简单了解 Nginx 中的内存池

栏目: 服务器 · Nginx · 发布时间: 6年前

内容简介:之前在内部分享了一次 Nginx,并写了一篇文章《现在继续来看看 Nginx 里面有意思的代码吧。第一个就是 Nginx 造的一堆轮子:内存池。

一、背景

之前在内部分享了一次 Nginx,并写了一篇文章《 一次 Nginx 分享 》。

现在继续来看看 Nginx 里面有意思的代码吧。

第一个就是 Nginx 造的一堆轮子:内存池。

二、内存池

Nginx 实现了一个简单的内存池,效率非常高,但是可用性非常差。

内存池,顾名思义,就是预先从操作系统里申请好内存,程序实际需要内存的时候,直接在用户态分配即可。

为啥要这样做呢?

因为默认分配内存涉及到与操作系统打交道,比较慢。

而自己管理内存则只需要第一次和操作系统打交道,之后都是在自己的数据上操作的,无非一些变量的加加减减,性能非常高。

具体怎么实现呢?

先申请一块比较大的内存,然后业务申请内存的时候,先保存可用内存的首地址,然后更新可用内存的偏移量。

伪代码如下:

void * ngx_palloc_s(ngx_pool_t* pool, size_t size){
    char* m = pool->d.last;
    p->d.last =  m + size;
    return m;
}

看了上面的代码,很多独立思考的人会有很多疑问。

1.比如内存不够了怎么办?

2.内存需要对齐怎么办?

3.内存怎么回收?

这三个问题也很容易回答。

三、内存不够了怎么办?

不够了创建新的内存池即可。

也就是内存池是一个链表,这样就不存在不够的问题了。

void * ngx_palloc_s(ngx_pool_t* pool, size_t size){
    char* m ;
    ngx_pool_t* p = pool->current; //第一个内存池
    do{
        m = p->d.last;
        if(p->d.end - m >= size){
            p->d.last =  m + size;
            return m;
        }
        p = p->d.next; //下一个内存池
    }while(p);
    //现有的内存池都不够,创建新的内存池
    return ngx_palloc_n(pool, size);
}

创建新内存池的代码也很简单。

调用系统的申请内存函数,然后把新内存池挂在链表的最后面。

这里面有一个优化,由于链表要在最后插入一个元素的时候,需要遍历链表。

nginx就在每个内存池上加了一个计数器,如果一个内存池被遍历四次,就把当前内存池设置为current。

这样就可以保证内存池有效链表节点的个数不会超过7个。

void* ngx_palloc_b(ngx_pool* pool, size_t size){
    ngx_pool_t* new = new_pool();
    for(p p pool->currentl p_d.next; p = p->d.next){
        if(p->d.failed++ > 4){
            pool->current = p->d.nexr;
        }
    }
    p->d.next = new;//插入到最后
}

四、内存需要对齐怎么办?

那就先对齐内存,在分配空间。

void * ngx_palloc_s(ngx_pool_t* pool, size_t size){
    char* m = pool->d.last;
    m = ngx_align(m);
    p->d.last =  m + size;
    return m;
}

可能有人会问:为什么要对齐内存?

这个涉及到的学问就有意思了。

第一个原因是为了性能。

因为 CPU 读数据就是字节内存对齐的。

假设分配的内存不对齐,我们的地址从 0x9 开始,要读 8 字节,则这个数据跨越了两个对齐的空间。

CPU 要读这个数据就需要两次。

第二个是为了方便计算地址。

比如对于整数数组,我们使用下标偏移的时候,每次加的是 4 字节。

换算为数学公式就是 a[n] = a + n * 4

如果字节对齐的话,就可以使用位操作来快速计算 a[n] = ((a>>2) + n) << 2;

第三是很多CPU只支持字节对齐的程序。

这个就比较霸道了,硬件不支持不对齐的程序,那我们只好对齐了。

具体可以参考这个地址吧, https://www.ibm.com/developerworks/library/pa-dalign/

五、内存怎么回收?

这个是最魔幻的地方。

答案是业务自己回收,这里不提供回收函数。

吐血。。。

那随便找个使用内存池的代码,看看怎么回收的吧。

array 数据结构为例。

简单了解 Nginx 中的内存池

代码中可以看到,回收的时候有个判断。

回收的数据必须是内存池最后一个分配的内存才可以回收。

这个就要求业务自己严格保证申请内存的顺序和回收内存的顺序完全相反了。

一丁点都不能错,错一个就全部不能回收了。

另外,再考虑我们分配内存的时候曾有过内存对齐操作。

如果分配内存时进行了内存对齐,将永远也满足不了这个回收条件了,也就是永远也回收不了了。

六、最后

看到这里, Nginx 的内存池就看完了。

是不是实现的特别简单、性能特别高。

但是也应了我上篇文章《 一次 Nginx 分享 》说的那句话:

而 Nginx 为了提高性能,所有的功能都自己来实现了。  
比如 SLAB 内存管理,array、list、hash、红黑树、regex等等。  
自己实现的时候,仅仅实现自己需要的功能。  
这样做出来的功能没有那么通用,但是也没有冗余,自己使用时性能会更高。

还有这句话:

对于任何系统,不管如何高性能,都存在其局限性,都有对应的使用场景。  
越过了使用场景,可能就不是高性能了,或者良好的模块设计就不那么良好了。  
Nginx 是如此, Redis 依旧如此。

本文首发于公众号:天空的代码世界,微信号:tiankonguse-code。


以上所述就是小编给大家介绍的《简单了解 Nginx 中的内存池》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Fluent Python

Fluent Python

Luciano Ramalho / O'Reilly Media / 2015-8-20 / USD 39.99

Learn how to write idiomatic, effective Python code by leveraging its best features. Python's simplicity quickly lets you become productive with it, but this often means you aren’t using everything th......一起来看看 《Fluent Python》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

MD5 加密
MD5 加密

MD5 加密工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具