標籤:style class blog code http tar
zmalloc.h的內容如下:
1 void *zmalloc(size_t size);2 void *zcalloc(size_t size);3 void *zrealloc(void *ptr, size_t size);4 void zfree(void *ptr);5 char *zstrdup(const char *s);6 size_t zmalloc_used_memory(void);7 void zmalloc_enable_thread_safeness(void);8 float zmalloc_get_fragmentation_ratio(void);9 size_t zmalloc_get_rss(void);10 size_t zmalloc_allocations_for_size(size_t size);11 12 #define ZMALLOC_MAX_ALLOC_STAT 256
就這麼幾行
。這是redis的記憶體管理介面。zmalloc,zcalloc,zrealloc和zfree分別對應c庫中的malloc,calloc,realloc和free
。zstrdup用於產生一個字串的拷貝。後面的幾個函數用於擷取記憶體使用量資訊,後面會詳細介紹。
在看zmalloc.c的源碼之前,先看看redis是怎樣管理記憶體的
。redis為了方便記憶體的管理,在分配一塊記憶體之後,會將這塊記憶體的大小存入記憶體塊的頭部。
real_ptr是redis調用malloc後返回的指標
。redis將記憶體塊的大小size存入頭部,size所佔據的記憶體大小是已知的,為size_t類型的長度,然後返回ret_ptr
。當需要釋放記憶體的時候,ret_ptr被傳給記憶體管理程式。通過ret_ptr,程式可以很容易的算出real_ptr的值,然後將real_ptr傳給free釋放記憶體
。
redis會記錄所有的記憶體配置情況。redis定義一個數組,這個數組的長度為ZMALLOC_MAX_ALLOC_STAT。數組的每一個元素代表當前程式所分配的記憶體塊的個數,且記憶體塊的大小為該元素的下標
。在程式中,這個數組為zmalloc_allocations。zmalloc_allocations[16]代表已經分配的長度為16bytes的記憶體塊的個數
。zmalloc.c中有一個靜態變數used_memory用來記錄當前分配的記憶體總大小
。
下面開始分析代碼。
首先是定義宏PREFIX_SIZE:
1 #ifdef HAVE_MALLOC_SIZE2 #define PREFIX_SIZE (0)3 #else4 #if defined(__sun)5 #define PREFIX_SIZE (sizeof(long long))6 #else7 #define PREFIX_SIZE (sizeof(size_t))8 #endif9 #endif
如果定義了HAVE_MALLOC_SIZE,那麼PREFIX_SIZE就為0
。這裡,HAVE_MALLOC_SIZE用來確定系統是否有函數malloc_size。這個HAVE_MALLOC_SIZE宏在config
.h中定義,如下:
1 /* Use tcmalloc‘s malloc_size() when available.2 * When tcmalloc is used, native OSX malloc_size() may never be used because3 * this expects a different allocation scheme. Therefore, *exclusively* use4 * either tcmalloc or OSX‘s malloc_size()! */5 #if defined(USE_TCMALLOC)6 #include <google/tcmalloc.h>7 #if TC_VERSION_MAJOR >= 1 && TC_VERSION_MINOR >= 68 #define HAVE_MALLOC_SIZE 19 #define redis_malloc_size(p) tc_malloc_size(p)10 #endif11 #elif defined(__APPLE__)12 #include <malloc/malloc.h>13 #define HAVE_MALLOC_SIZE 114 #define redis_malloc_size(p) malloc_size(p)15 #endif
如果使用google的tcmalloc庫,那麼,redis_malloc_size就對應與tcmalloc庫的tc_malloc_size函數
。如果是在apple的mac上編譯,那麼redis_malloc_size就對應與malloc_size
。redis_malloc_size的功能是獲得參數p所指向的記憶體塊的大小
。tcmalloc庫在google的google-perftools 庫中,據說這個庫在記憶體管理的效率上很驚豔
。不過這個庫是c++寫的,而redis是c寫的,兩者揉一起還是有點不給力阿
。
。。
如果沒有malloc_size函數,那麼在Solaris系統上,用long long類型的長度來定義PREFIX_SIZE,其他系統為size_t的長度。
接著,定義下面這些宏。這些宏的作用是如果使用tcmalloc庫,那麼將庫中的分配函數對應到標準庫上
。後面的函數可直接使用標準庫函數的名稱。在更換庫的時候不需要更改。
1 /* Explicitly override malloc/free etc when using tcmalloc. */2 #if defined(USE_TCMALLOC)3 #define malloc(size) tc_malloc(size)4 #define calloc(count,size) tc_calloc(count,size)5 #define realloc(ptr,size) tc_realloc(ptr,size)6 #define free(ptr) tc_free(ptr)7 #endif下面的兩個宏用於更新zmalloc_allocations數組。update_zmalloc_stat_alloc用於在分配記憶體的時候更新已指派大小,update_zmalloc_stat_free用於在釋放記憶體的時候刪除對應的記錄。1 #define update_zmalloc_stat_alloc(__n,__size) do { 2 size_t _n = (__n); 3 size_t _stat_slot = (__size < ZMALLOC_MAX_ALLOC_STAT) ? __size : ZMALLOC_MAX_ALLOC_STAT; 4 if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); 5 if (zmalloc_thread_safe) { 6 pthread_mutex_lock(&used_memory_mutex); 7 used_memory += _n; 8 zmalloc_allocations[_stat_slot]++; 9 pthread_mutex_unlock(&used_memory_mutex); 10 } else { 11 used_memory += _n; 12 zmalloc_allocations[_stat_slot]++; 13 } 14 } while(0)update_zmalloc_stat_alloc的第一個參數__n是從系統那實際獲得的記憶體大小,第二個參數是程式請求的記憶體大小
。update_zmalloc_stat_alloc首先判斷程式請求的記憶體大小在zmalloc_allocations數組中對應的下標
。如果記憶體大小大於zmalloc_allocations數組的長度-1,那麼其對應的下標是最後一個。然後,將實際分配的記憶體大小對齊為long類型長度的整數倍(malloc通常會考慮對齊問題,實際分配的記憶體大小也會因對齊而有所出入,後文會介紹)
。最後,在used_memory記錄實際分配的大小,在zmalloc_allocations對應位置加一
。這裡如果設定為安全執行緒,那麼在記錄之前要對兩個靜態變數加鎖
。
1 #define update_zmalloc_stat_free(__n) do { 2 size_t _n = (__n); 3 if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); 4 if (zmalloc_thread_safe) { 5 pthread_mutex_lock(&used_memory_mutex); 6 used_memory -= _n; 7 pthread_mutex_unlock(&used_memory_mutex); 8 } else { 9 used_memory -= _n; 10 } 11 } while(0)update_zmalloc_stat_free和update_zmalloc_stat_alloc差不多,但僅僅減少了used_memory的值
。
對於zmalloc,zalloc,zrealloc和zfree這幾個函數,僅僅是對標準庫的函數的簡單的封裝
。所做的工作除了調用標準庫(也可能是tcmalloc庫)的函數分配記憶體外,就是對每次分配和釋放記憶體做合適的記錄
。如果系統中有malloc_size函數,那麼直接調用前面的那兩個宏,沒什麼可講的
。如果沒有malloc_size函數,那麼需要在所分配的記憶體頭部的PREFIX_SIZE大小的地區內,記錄記憶體塊的大小
。代碼很簡單,就一句:1 *((size_t*)ptr) = size;現將ptr轉換成size_t類型的指標,然後將size的值賦給其指向的記憶體
。筆者感覺沒有必要在前面定義PREFIX_SIZE的時候區分系統,因為這裡直接寫入程式碼了記憶體大小的類型為size_t
。前面的宏判斷有點多此一舉了
。這裡的size是返回的記憶體地區的大小,不包括儲存大小的頭部。
讀取記憶體塊大小需要兩步:1 realptr = (char*)ptr-PREFIX_SIZE;
2 oldsize = *((size_t*)realptr);程式傳進來的是ret_ptr,通過減去PREFIX_SIZE得到real_ptr
。從real_ptr所指向的記憶體中讀取大小即可。
其餘的幾個函數也很直接,沒什麼好說的
。最後講一講zmalloc_get_rss()函數。這個函數用來擷取進程的RSS。神馬是RSS?google reader那個?顯然不是。
。
。全稱為Resident Set Size,指實際使用實體記憶體(包含共用庫佔用的記憶體)
。在linux系統中,可以通過讀取/proc/pid/stat檔案擷取,pid為當前進程的進程號
。讀取到的不是byte數,而是記憶體頁數。通過系統調用sysconf(_SC_PAGESIZE)可以獲得當前系統的記憶體頁大小
。Unix系統貌似可以直接通過task_info直接擷取,比linux系統簡單的多。
獲得進程的RSS後,可以計算目前資料的記憶體片段大小,直接用rss除以used_memory
。rss包含進程的所有記憶體使用量,包括代碼,共用庫,堆棧等。但是由於通常情況下redis在記憶體中資料的量要遠遠大於這些資料所佔用的記憶體,因此這個簡單的計算還是比較準確的
。
這裡有一個問題,程式都是用多少記憶體就分配多少記憶體,哪來的記憶體片段?其實,當調用malloc的時候,malloc並不是嚴格按照參數的值來分配記憶體
。比如,程式只請求一個byte的記憶體,malloc不會就只分配一個byte,通常,基於記憶體對齊等方面的考慮,malloc會分配4個byte
。這樣,如果程式中大量請求1byte記憶體,那麼實際使用的是所請求的4倍。malloc進行小記憶體配置是很浪費的
。所以,片段就在這裡產生了。
總的來說,redis的記憶體管理簡單粗暴,沒有神馬複雜的引用計數等技術
。但是,很多時候,簡單的往往是高效且合理的。redis記憶體的中資料通常會是幾個G,這個方法快速,統計結果也很精確
。
coding trick:
#define update_zmalloc_stat_alloc(__n) do { \ size_t _n = (__n); if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); if (zmalloc_thread_safe) { update_zmalloc_stat_add(_n); } else { used_memory += _n; } } while(0)先對_n的低位向上取整,最後_n變為sizeof(long)的倍數,比如對於32位系統,sizeof(long) == 100(二進位),_n向上取整之後,低兩位都變為0。分配的空間與記憶體對齊