我們直接來看 Redis 原始碼(不是最新版本)中自訂的 zmalloc 函數,該函數與 malloc 等常規函數的使用方式完全一致,不同的在於其內部的具體實作細節。
# void *zmalloc(size_t size) {
// 分配記憶體;
# void *ptr = malloc(size PREFIX_SIZE);
# // 分配失敗拋出例外;
if (!ptr) zmalloc_oom_handler(size);
# // 系統是否可以使用”malloc_size“函數?
#ifdef HAVE_MALLOC_SIZE
update_zmalloc_stat_alloc(zmalloc_size(ptr));
return ptr;
# #else
# // 在資料域中保存分配資料的實際大小;
# *((size_t*)ptr) = size;
// 計算對齊後的記憶體使用大小,並更新」used_memory「變數;
update_zmalloc_stat_alloc(size PREFIX_SIZE);
// 傳回資料體的初始位置;
return (char*)ptr PREFIX_SIZE;
#endif
# }
其實,標準函式庫中的 malloc 函數已經能夠自動為分配的記憶體實作對齊,因此 zmalloc 方法在這裡其主要目的是為了能夠精確地計算每一次資料儲存時所分配的記憶體大小。在每一次分配記憶體時,zmalloc 都會在該次分配的資料記憶體大小的基礎上再加上一個PREFIX_SIZE 大小的額外記憶體空間,這個PREFIX_SIZE 巨集代表了目前系統的最大記憶體定址空間大小(size_t),其依賴具體係統的類型不同而不同。這裡我們可以簡稱這個 PREFIX_SIZE 大小的空間為一個儲存單元的「資料頭」部分。
初版 Redis 的儲存單元結構
# 如上圖所示,透過*((size_t*)ptr) = size; 語句,Redis 在目前分配記憶體區塊的前PREFIX_SIZE 個位元組,即資料頭內儲存了本次實際分配的資料區塊大小,而在後面」size「 大小的記憶體空間才真正存放了二進位的資料實體。這裡名為 update_zmalloc_stat_alloc 的函數在其內部會維護一個名為 used_memory 的全域變量,該變數累積了每次新分配的記憶體大小。函數在最後傳回了一個偏移的指針,指向了目前分配記憶體的資料體部分。 update_zmalloc_stat_alloc 函數的具體實作細節如下。
#define update_zmalloc_stat_alloc(__n) do {
# size_t _n = (__n);
// 手動記憶體補齊;
if (_n&(sizeof(long)-1)) _n = sizeof(long)-(_n&(sizeof(long)-1));
# atomicIncr(used_memory, __n);
# } while(0)
這裡要注意的重點是 _n = sizeof(long)-(_n&(sizeof(long)-1)); 這行語句。整個巨集函數首先判斷本次分配的記憶體大小是否為sizeof(long) 大小的整數倍(64位元機對應8位元組的記憶體對齊;32位元機則對應4位元組的記憶體對齊),如果不是則透過我們先前給出的語句在該資料段後面加上對應的佔位空間來補足位數以滿足記憶體對齊(4/8位元組)的要求。最後的 atomicIncr 函數用來在保證線程安全的情況下更新全域的 used_memory 變數值。
而該版本 Redis 中記憶體釋放與其記憶體分配的過程則正好相反。如下所示程式碼為對應 ”zfree“ 函數的實作細節。首先該函數透過(char*)ptr-PREFIX_SIZE 語句(向記憶體低位址移動)指向了包含有該資料塊實際佔用大小的資料域首位址,然後透過*((size_t*)realptr) 語句獲得到了該數據區塊分配的真實記憶體大小(不包含記憶體對齊區域)。最後再透過 update_zmalloc_stat_free 函數來更新全域變數 used_memory 的值,並釋放該段記憶體。
void zfree(void *ptr) {
#ifndef HAVE_MALLOC_SIZE
void *realptr;
size_t oldsize;
#endif
# if (ptr == NULL) return;
# #ifdef HAVE_MALLOC_SIZE
update_zmalloc_stat_free(zmalloc_size(ptr));
free(ptr);
#else
# realptr = (char*)ptr-PREFIX_SIZE;
# oldsize = *((size_t*)realptr);
# update_zmalloc_stat_free(oldsize PREFIX_SIZE);
free(realptr);
#endif
# }
如下所示,這裡如果我們再來看 update_zmalloc_stat_free 函數的實作細節,你會發現它與先前的 update_zmalloc_stat_alloc 函數其執行過程類似。透過計算需要補足的記憶體位元組大小,並從 used_memory 變數中減去對應大小的記憶體空間,即可實現對記憶體空間使用率的精確計算。
#define update_zmalloc_stat_free(__n) do { \
size_t _n = (__n); \
if (_n&(sizeof(long)-1)) _n = sizeof(long)-(_n&(sizeof(long)-1)); \
# atomicDecr(used_memory,__n); \
} while(0)
以上是Redis的zmalloc函數實例分析的詳細內容。更多資訊請關注PHP中文網其他相關文章!