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。