【高效server實踐】--memcached內存管理

    Memecache內存管理是採取預分配的形式,避免優先避免頻繁malloc和free帶來的內存碎片。Memcache是駐歐洲斷裂slab的形式來管理內存:每個slab頁默認大小爲1M,不同的slab裏會1到n個分割成大小不同chunk內存塊,chunk是實際存儲數據的最小單元。存儲數據的時候根據數據的大小選擇從相應的slab分配空閒chunk來存儲。

       memcached -u root -f 2 -vv;可以查看Memcache的slabs分配情況

1:Memecache內存管理的代碼都在slabs.c裏面,slabclass_t是內存管理結構體,memcache本上維護了一個slabclass_t數組。

typedef struct {

    unsigned int size;      /* 數據存儲單元item的大小*/

    unsigned int perslab;   /* 數據存儲單元item的數量 */

    void **slots;           /* 空閒item鏈表*/

    unsigned int sl_total;  /* 已經分配的空閒item數量 */

    unsigned int sl_curr;   /* 空閒item的數量(當前空閒item的位置) */

    void *end_page_ptr;         /* 最後一個頁面中空閒item的位置 */

    unsigned int end_page_free; /* 最後一個頁面item的數量 */

    unsigned int slabs;     /* slab數量 */

    void **slab_list;       /* slab指針數組 */

    unsigned int list_size; /* 已經分配slab指針數組大小*/

    unsigned int killing;  /* index+1 of dying slab, or zero if none */

       } slabclass_t;

       2:memcache啓動時會調用slabs_init初始化。limit是Memcache最大使用內存的大小,factor是slab_class item大小的增長因子,可以通過memcache -n參數調整。memcache還會調用slabs_preallocate爲每個slab_class預分配一個默認1M的slab,以避免在memcache開始階段頻繁的新分配slab。

 void slabs_init(size_t limit, double factor) {

    int i = POWER_SMALLEST - 1;

    unsigned int size = sizeof(item) + settings.chunk_size;

 

     /* Factor of 2.0 means use the default memcached behavior */

         if (factor == 2.0 && size < 128)

              size = 128;

    mem_limit = limit;

    memset(slabclass, 0, sizeof(slabclass));

    while (++i < POWER_LARGEST && size <= POWER_BLOCK / 2) {

        /* Make sure items are always n-byte aligned */

        if (size % CHUNK_ALIGN_BYTES)

            size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES); //內存對齊

 

        slabclass[i].size = size;

        slabclass[i].perslab = POWER_BLOCK / slabclass[i].size;

        size *= factor;//chunk大小按增長因子增長

        if (settings.verbose > 1) {

            fprintf(stderr, "slab class %3d: chunk size %6d perslab %5d\n",

                    i, slabclass[i].size, slabclass[i].perslab);

        }  

    }  

    power_largest = i;

    slabclass[power_largest].size = POWER_BLOCK;

    slabclass[power_largest].perslab = 1;

#ifndef DONT_PREALLOC_SLABS

    {

        char *pre_alloc = getenv("T_MEMD_SLABS_ALLOC");

        if (!pre_alloc || atoi(pre_alloc)) {

            slabs_preallocate(limit / POWER_BLOCK);

        }

    }

#endif

}

void slabs_preallocate (unsigned int maxslabs) {

    int i;

    unsigned int prealloc = 0;

 

    /* pre-allocate a 1MB slab in every size class so people don't get

       confused by non-intuitive "SERVER_ERROR out of memory"

       messages.  this is the most common question on the mailing

       list.  if you really don't want this, you can rebuild without

       these three lines.  */

 

    for(i=POWER_SMALLEST; i<=POWER_LARGEST; i++) {

        if (++prealloc > maxslabs)

            return;

        slabs_newslab(i);
    }

}

  3:當需要獲取內存單元時調用slabs_alloc,若當前沒有空間的chunk則調用slabs_newslab生成新的slab,再把空閒的slab放到未分配空閒鏈表p->slots中。

 void *slabs_alloc(size_t size) {

    slabclass_t *p;

    unsigned char id = slabs_clsid(size);

    if (id < POWER_SMALLEST || id > power_largest)

        return 0;

    p = &slabclass[id];

    assert(p->sl_curr == 0 || ((item*)p->slots[p->sl_curr-1])->slabs_clsid == 0);

#ifdef USE_SYSTEM_MALLOC

    if (mem_limit && mem_malloced + size > mem_limit)

        return 0;

    mem_malloced += size;

    return malloc(size);

#endif

    /* fail unless we have space at the end of a recently allocated page,

       we have something on our freelist, or we could allocate a new page */

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

        return 0;

    /* return off our freelist, if we have one */

    if (p->sl_curr)

        return p->slots[--p->sl_curr];

    /* if we recently allocated a whole page, return from that */

    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;
    }
   return 0;  /* shouldn't ever get here */
}

int slabs_newslab(unsigned int id) {

    slabclass_t *p = &slabclass[id];

#ifdef ALLOW_SLABS_REASSIGN

    int len = POWER_BLOCK;

#else

    int len = p->size * p->perslab;

#endif

    char *ptr;

 

    if (mem_limit && mem_malloced + len > mem_limit && p->slabs > 0)

        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 = p->perslab;

 

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

    mem_malloced += len;

    return 1;

}

4:內存的釋放調用slabs_free,根據代碼可以看出,memcache不會真正地去釋放內存,還是把要釋放的那部分內存重新放到slab_class的空閒chunk鏈表slots

void slabs_free(void *ptr, size_t size) {

    unsigned char id = slabs_clsid(size);

    slabclass_t *p;

    assert(((item *)ptr)->slabs_clsid==0);

    assert(id >= POWER_SMALLEST && id <= power_largest);

    if (id < POWER_SMALLEST || id > power_largest)

        return;

    p = &slabclass[id];

#ifdef USE_SYSTEM_MALLOC

    mem_malloced -= size;

    free(ptr);

    return;

#endif

    if (p->sl_curr == p->sl_total) { /* need more space on the free list */

        int new_size = p->sl_total ? p->sl_total*2 : 16;  /* 16 is arbitrary */

        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;

}

 5:memcache這種內存管理方式也會造成一定的內存浪費:

  1):當數據大小<chunk Size,chunk的空間未完全利用,超成一定的內存浪費

  2):現默認一個slabclass_t的大小是1M,如果slabclass_t的大小不是chunk的整數倍,則餘下的內存空間將被浪費。

  3):Memcache是默認不開啓slab resign的,也就是已分配給一個slab,即使該slab不用,也不會分配給其它slab。

      


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章