理解memcached原始碼 - Slab I
原文發表在:
ofollow,noindex"> https:// holmeshe.me/understandi ng-memcached-source-code-I/Slab分配器 是這個快取系統的核心,並在很大程度上決定了核心資源 - 記憶體 - 的利用效率。其它的三個部分,
用來淘汰(超時)物件的LRU演算法;和
基於lebevent的事件驅動;以及
用於分佈資料的一致性雜湊,
可以看作是圍繞Slab來開發的。
在其他系統,比如核心,都能看到 Slab 分配器 的身影。無論它出現在哪裡,都是為了對抗同一個效能問題, 記憶體碎片 。而本文就主要討論 Slab 分配器 在memcached 中的實現(廢話)。
memcached version: 1.4.28
首先我們來回答一些問題。
前言
啥是Slab
Slab翻譯過來就是(一塊) 板子 ,具體來說,它是是被預先分配好的,大小為1M的記憶體塊。這些板子可以被進一步分割成一些相同大小的小塊,物件就存(寫)在每一個小塊上面。板子們會根據所儲存物件的大小分成 Slab組 ( slab class )。
剛剛提到的記憶體碎片是啥
進一步來講, Slab 分配器 解決的其實是 內在碎片 (internal memory fragmentation) 。這種碎片存在於分配的記憶體單元的內部。拿核心來說,記憶體單元叫 頁(page) ,所有的記憶體分配的請求本質上都是在 頁 裡面拿走一塊,同時產生的碎片也就自然產生於每 頁 的內部了。
和內在碎片不一樣, 外在碎片(external memory fragmentation) 則存在於分配的記憶體單元之間。解決外在碎片的一般做法則是用 buddy ,就不在本文範圍內了。
我們再看下製造記憶體碎片過程,
1) malloc
一堆小物件,
2)隨機 free
一些上述小物件。
於是本來是整片的記憶體就會出現很多空洞,這些空洞,或者說 碎片 ,因為太小而且分散,大概率永遠無法被後續的 malloc
利用。
記憶體碎片引起的問題
繼續往後說。這些碎片由於不能被 malloc
使用,基本也就和 記憶體洩漏 差不多了。引發的具體問題也差不多 - 定期重啟。
怎麼辦
Slab 分配器 並不消除 記憶體碎片 ,而是將它們收斂起來,並鎖定在某個固定的記憶體區域。具體來說,1)將大小相近的物件分 組 ;2)同一 組 的的物件只會用該組的 Slab ( slab class )來分配記憶體。
接下來看程式碼。
reminder: memcached version is 1.4.28
核心資料結構:
typedef struct { unsigned int size;/* sizes of items */ unsigned int perslab;/* how many items per slab */ void *slots;/* list of item ptrs */ unsigned int sl_curr;/* total free items in list */ unsigned int slabs;/* how many slabs were allocated for this class */ void **slab_list;/* array of slab pointers */ unsigned int list_size; /* size of prev array */ size_t requested; /* The number of requested bytes */ } slabclass_t; static slabclass_t slabclass[MAX_NUMBER_OF_SLAB_CLASSES];
模組初始化
本節我們來看 slabs_init
,和 slabclass[MAX_NUMBER_OF_SLAB_CLASSES]
的初始化。 這個函式主要給 slabclass_t.size
,和 slabclass_t.perslab
賦值。第一個域表示 Slab組 所對應的物件大小,第二個域則表示一個 Slab 上可以放多少個該類的物件。最後, slabs_init
是在系統初始化的過程被呼叫(如以下程式碼),
… assoc_init(settings.hashpower_init); conn_init(); slabs_init(settings.maxbytes, settings.factor, preallocate, use_slab_sizes ? slab_sizes : NULL); …
在這個階段, slab_sizes
和 settings.factor
共同決定了後續邏輯的走向,並且確定各個 Slab組 所儲存的物件大小,
如果 slab_sizes 不是 NULL
,用此陣列的裡面的值直接初始化各 Slab組 所對應的物件大小(支線a);
反之,則用 base size × n × settings.factor
來初始化上述的目標。這裡 n 是 slabclass
的下標(支線b)。
除了寫死的預設值,上述兩個變數也能用命令列引數賦值。
… case 'f': settings.factor = atof(optarg); if (settings.factor <= 1.0) { fprintf(stderr, "Factor must be greater than 1\n"); return 1; } break; … case 'o': … case SLAB_SIZES: if (_parse_slab_sizes(subopts_value, slab_sizes)) { use_slab_sizes = true; } else { return 1; } break; …
本函式的另外兩個引數 settings.maxbytes
和 preallocate
,會在後續討論。這裡我們假設 preallocate
為 false
,並忽略其對應的邏輯。
下面我們來看 slabs_init
本身。
void slabs_init(const size_t limit, const double factor, const bool prealloc, const uint32_t *slab_sizes) { int i = POWER_SMALLEST /* scr: 1 */ - 1; unsigned int size = sizeof(item) + settings.chunk_size; // scr: ---------> b 1) ... memset(slabclass, 0, sizeof(slabclass)); while (++i < MAX_NUMBER_OF_SLAB_CLASSES-1) { if (slab_sizes != NULL) { // scr: -----------------------------------> a 1) if (slab_sizes[i-1] == 0) break; size = slab_sizes[i-1]; } else if (size >= settings.item_size_max / factor) { break; } /* Make sure items are always n-byte aligned */ if (size % CHUNK_ALIGN_BYTES) // scr: ---------------------------------> 2) size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES); slabclass[i].size = size; slabclass[i].perslab = settings.item_size_max / slabclass[i].size; // -> 3) if (slab_sizes == NULL) size *= factor; // scr: -----------------------------------------> b 4) if (settings.verbose > 1) { fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n", i, slabclass[i].size, slabclass[i].perslab); } } // scr: -------------------------------------------------------------------> 5) power_largest = i; slabclass[power_largest].size = settings.item_size_max; slabclass[power_largest].perslab = 1; ... }
支線 a
1)使用 slab_sizes
裡面的值;
2) 將 size
用 CHUNK_ALIGN_BYTES
對其,並賦值給 slabclass[i].size
;
3)計算 slabclass[i].perslab
;
5)用 settings.item_size_max
初始化最後一個 Slab組 。
這裡要注意 settings.item_size_max
是 slab 本身的大小,也即是 memcached 能存的最大物件。類似的, settings.item_size_max
也可以在執行時確定
case 'I': buf = strdup(optarg); unit = buf[strlen(buf)-1]; if (unit == 'k' || unit == 'm' || unit == 'K' || unit == 'M') { buf[strlen(buf)-1] = '\0'; size_max = atoi(buf); if (unit == 'k' || unit == 'K') size_max *= 1024; if (unit == 'm' || unit == 'M') size_max *= 1024 * 1024; settings.item_size_max = size_max; } else { settings.item_size_max = atoi(buf); } free(buf); if (settings.item_size_max < 1024) { fprintf(stderr, "Item max size cannot be less than 1024 bytes.\n"); return 1; } if (settings.item_size_max > 1024 * 1024 * 128) { fprintf(stderr, "Cannot set item size limit higher than 128 mb.\n"); return 1; } if (settings.item_size_max > 1024 * 1024) { fprintf(stderr, "WARNING: Setting item max size above 1MB is not" " recommended!\n" " Raising this limit increases the minimum memory requirements\n" " and will decrease your memory efficiency.\n" ); } break;
支線 b
1)用 settings.chunk_size
加上給每個物件附著的元資料(meta data)來計算基礎大小(物件 item
會在後面討論);
2)將 size
用 CHUNK_ALIGN_BYTES
對其,並賦值給 slabclass[i].size
(同支線a);
3)計算 slabclass[i].perslab
(同支線a);
4) 用 factor
( settings.factor
) 計算下一個 Slab組 的大小;
5) 用 settings.item_size_max 初始化最後一個 Slab組 。