【算法】memcached slabs内存分配算法详解

原文:http://blog.sina.com.cn/s/blog_7530db6f0100omfn.html

Memcached Slab算法是根据powers of 2来将1MB的内存块划分成多个小内存块, 而这1MB的内存块称为页:

Powers of 2是2的n次方的意思,例如:2的0次方是1,2的1次方是2,2的2次方是4,2的3次方是8等等。

而将1MB的内存按2的n次方划分可以划分成20种不同的内存块,因为2的20次方是1MB(1048576)。所以可以说,memcached管理着20种不同的内存块的集合。

memcached <wbr>slabs内存分配算法详解
 

 

 

 

而memcached是通过slabclass_t结构体来管理这些小内存块的, slabclass_t的定义如下:

typedef struct
{

    unsigned int  size;
    unsigned int  perslab;

    void  **slots;
    unsigned int  sl_total;
    unsigned int  sl_curr;

    void  *end_page_ptr;
    unsigned int  end_page_free;
    unsigned int  slabs;

    void  **slab_list;
    unsigned int  list_size;
    unsigned int  killing;

}  slabclass_t;

根据上面的结构体,我们完全可以指定memcached中slabs实际的内存分配。

 

2. slab 内存分配单位


  • Memcached的内存分配是以slab为单位的。默认情况下,每个slab大小为1M。
  • slabclass数组初始化的时候,每个slabclass_t都会分配一个1M大小的slab。
  • 当某个slabclass_t结构上的内存不够的时候(freelist空闲列表为空),则会分配一个slab给这个slabclass_t结构。
  • 一旦slab分配后,不可回收。
  • slab会被切分为N个小的内存块,这个小的内存块的大小取决于slabclass_t结构上的size的大小。例如slabclass[0]上的size为103,则每个小的内存块大小为103byte。
  • 这些被切割的小的内存块,主要用来存储item。但是,存储的item,可能会比切割出来的内存块会小。因为这是为了防止内存碎片,虽然有一些内存的浪费。

 

现在再来看看slabs,一个slabs里面可以包含很多slab,slab可以分配的节点大小如同STL自由链表,例如分别是8、16字节等等。和STL中不同的是,STL中节点大小为8字节的,通常由一条链表构成,但是slab中节点为8字节的通常由多个链表构成,其中的一个链表叫做页。概念示意图如下所示。
这里写图片描述

slabclass和slab、item以及free list之间的关系:

通过item的size来选择slab_class的数据存储空间:

图片来自:https://blog.csdn.net/initphp/article/details/44888555

这里写图片描述

上图结构中可以知道,每一个slabclass_t管理一个slab,每个slab里面有多条链表,分为多个页。chunk_size对应此slab中节点可以分配的内存块大小。通过settings.chunk_size确定最小的chunk_size,默认是48字节,通过增加因子确定chunk_size如何递归。下面看看内存分配器初始化。

slabs缺点

这当然是内存池的通用缺点。稍微浪费点空间换更好的内存碎片。
Slab Allocator 解决了当初的内存碎片问题,由于分配的是特定长度的内存,因此无法有效利用分配的内存。例如,将100字节的数据缓存到128字节的chunk中,剩余的 28字节就浪费了。
这里写图片描述

 

 

 

(1)  slots指针指向的是内存分配器回收的小内存块的数组, sl_total保存了回收器的容量, 当回收器容量不足时, 需要重新分配更大的内存来作为回收器, sl_curr是当前回收器回收到的位置, 下一个回收的内存块就会放到这里:


memcached <wbr>slabs内存分配算法详解
 

(2)  end_page_ptr保存的是当前的空闲内存块, end_page_free保存的是当前空闲块的数量, 如果end_page_free等于0表示已经没有空闲内存块了, 需要向系统申请一块新的内存页.

memcached <wbr>slabs内存分配算法详解


(3)  slab_list保存的是申请的内存页, slabs保存的是已经申请的内存页数量. 

memcached <wbr>slabs内存分配算法详解

所以综合起来的总体结构图:


memcached <wbr>slabs内存分配算法详解
 

slab分配函数(slabs_alloc):

if (! (p->end_page_ptr || p->sl_curr || slabs_newslab(id)))
    return 0;


if (p->sl_curr)
    return p->slots[--p->sl_curr];

if (p->end_page_ptr)
{

    void *ptr = p->end_page_ptr;

    if (--p->end_page_free)
    {
        p->end_page_ptr += p->size;
    }
    else
    {
        p->end_page_ptr = 0;
    }

    return ptr;
}

函数中首先判断是否有空闲内存块, 或者回收过的内存块, 如果都没有就调用slabs_newslab()新建一个内存页; 然后优先分配回收过的内存块, 如果没有才去分配空闲的内存块.

slabs_newslab()函数是新建一个内存页.

int slabs_newslab(unsigned int id) 
{

   slabclass_t *p = &slabclass[id];
   int num = p->perslab;
   int len = POWER_BLOCK;
   char *ptr;

 

   if (mem_limit && mem_malloced + len > mem_limit)
       return 0;


   if (! grow_slab_list(id)) return 0;

  
   ptr = malloc(len);
   if (ptr == 0) return 0;
   memset(ptr, 0, len);

   p->end_page_ptr = ptr;
   p->end_page_free = num;


   p->slab_list[p->slabs++] = ptr;
   mem_malloced += len;

   return 1;

}

slabs_newslab()每次分配1MB作为内存页. 然后把end_page_ptr指向这个新的内存页.

slab回收函数(slabs_free):

void slabs_free(void *ptr, unsigned int size)
{

    unsigned char id = slabs_clsid(size);
    slabclass_t *p;

    if (id < POWER_SMALLEST || id > POWER_LARGEST)
        return;

    p = &slabclass[id];
    if (p->sl_curr == p->sl_total)
    {

        int new_size = p->sl_total ? p->sl_total * 2 : 16;
        void **new_slots = realloc(p->slots, new_size * sizeof(void *));

        if (new_slots == 0)
            return;

        p->slots = new_slots;
        p->sl_total = new_size;
    }

    p->slots[p->sl_curr++] = ptr;

    return;
}

回收时, 直接用回收器(slots)的指针指向这个内存块, 这样就完成回收操作. 如果回收器不够大, 就扩充回收器.

 

参考:https://blog.csdn.net/initphp/article/details/44888555

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值