標籤:linux c redis
記憶體配置對於C程式來說是一個核心問題,許多開源軟體都會針對自己軟體的需要定製自己的記憶體配置策略,redis也不例外。然而總的來說,redis並不是專門去管記憶體配置的東西,它的記憶體配置策略的最大特點在於加上了統計資訊,這一點很重要。畢竟,redis是一個記憶體資料庫,知道自己用了多少記憶體,還有多少記憶體可用是它非常需要關注的問題。我們來看zmalloc裡面的內容。
首先在zmalloc.h裡面
#if defined(USE_TCMALLOC)#define ZMALLOC_LIB ("tcmalloc-" __xstr(TC_VERSION_MAJOR) "." __xstr(TC_VERSION_MINOR))#include <google/tcmalloc.h>#if (TC_VERSION_MAJOR == 1 && TC_VERSION_MINOR >= 6) || (TC_VERSION_MAJOR > 1)#define HAVE_MALLOC_SIZE 1#define zmalloc_size(p) tc_malloc_size(p)#else#error "Newer version of tcmalloc required"#endif#elif defined(USE_JEMALLOC)#define ZMALLOC_LIB ("jemalloc-" __xstr(JEMALLOC_VERSION_MAJOR) "." __xstr(JEMALLOC_VERSION_MINOR) "." __xstr(JEMALLOC_VERSION_BUGFIX))#include <jemalloc/jemalloc.h>#if (JEMALLOC_VERSION_MAJOR == 2 && JEMALLOC_VERSION_MINOR >= 1) || (JEMALLOC_VERSION_MAJOR > 2)#define HAVE_MALLOC_SIZE 1#define zmalloc_size(p) je_malloc_usable_size(p)#else#error "Newer version of jemalloc required"#endif#elif defined(__APPLE__)#include <malloc/malloc.h>#define HAVE_MALLOC_SIZE 1#define zmalloc_size(p) malloc_size(p)#endif#ifndef ZMALLOC_LIB#define ZMALLOC_LIB "libc"#endif
這裡可以看到,對於redis的記憶體配置,有幾種可選策略,google的tmalloc,facebook的jemalloc,這兩者都是malloc中的優秀實現,各有千秋。關於更多的兩個malloc的討論可以參看博文http://blog.sina.com.cn/s/blog_51df3eae01016peu.html。此外,也可以選擇libc原生的malloc,但需要在每次記憶體配置前加4個位元組記錄記憶體配置的長度資訊,以實現malloc_size的功能。源碼中已經內建了jemalloc的源碼,可以看出官方還是比較推崇使用jemalloc的。
下面看看最重要的部分是即記憶體的使用統計是如何?的:
#if defined(__ATOMIC_RELAXED)#define update_zmalloc_stat_add(__n) __atomic_add_fetch(&used_memory, (__n), __ATOMIC_RELAXED)#define update_zmalloc_stat_sub(__n) __atomic_sub_fetch(&used_memory, (__n), __ATOMIC_RELAXED)#elif defined(HAVE_ATOMIC)#define update_zmalloc_stat_add(__n) __sync_add_and_fetch(&used_memory, (__n))#define update_zmalloc_stat_sub(__n) __sync_sub_and_fetch(&used_memory, (__n))#else#define update_zmalloc_stat_add(__n) do { pthread_mutex_lock(&used_memory_mutex); used_memory += (__n); pthread_mutex_unlock(&used_memory_mutex); } while(0)#define update_zmalloc_stat_sub(__n) do { pthread_mutex_lock(&used_memory_mutex); used_memory -= (__n); pthread_mutex_unlock(&used_memory_mutex); } while(0)#endif#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)#define update_zmalloc_stat_free(__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_sub(_n); } else { used_memory -= _n; } } while(0)
我們使用的大部分系統中都已經提供了關於安全執行緒的加減法法函數,如__atomic_add_fetch、__atomic_sub_fetch或者__sync_add_and_fetch,__sync_sub_and_fetch,如果沒實現也不要緊,zmalloc裡面提供了如上替代的方案,並使用宏函數進行統一起來。替代方案中我們看到了比較有意思的現象,你分配了n大小的記憶體,但是在統計的時候不是加上n,而是if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1))這樣一種方式。這是為什麼呢,經過查閱資料,這是記憶體對齊的緣故,所謂記憶體對齊,就是系統在實際分配記憶體的時候出於效率上的考慮,會多分配一些記憶體給指標。對於32位機來說,4位元組對齊能夠使cpu訪問速度提高,比如說一個long類型的變數,如果跨越了4位元組邊界儲存,那麼cpu要讀取兩次,這樣效率就低了。但是在32位機中使用1位元組或者2位元組對齊,反而會使變數訪問速度降低。所以這要考慮處理器類型,另外還得考慮編譯器的類型。在vc中預設是4位元組對齊的,GNU gcc 也是預設4位元組對齊。所以在預設環境下,就以4位元組對齊來計算其實際分配大小。
設計上採取了一個靜態變數來表示是否啟用安全執行緒,一個代表使用的記憶體數量,一個為線程鎖用來進行在統計時加鎖。
static size_t used_memory = 0;
static int zmalloc_thread_safe = 0;
pthread_mutex_t used_memory_mutex = PTHREAD_MUTEX_INITIALIZER;
系統實現四個核心的分配函數zmalloc,zcalloc,zrelloc,zfree,對應標準的malloc,calloc,relloc,free。以及記憶體空間統計等,安全執行緒控制等函數,這部分源碼比較容易理解不細說。另外要說一下其zmalloc_get_rss,它的作用是在unix系列系統的proc/pid/state檔案下直接查詢出記憶體的使用方式(這一部分是進程所有分配的記憶體包括全域tatic和棧中的變數),以及相除擷取其記憶體使用量效率。最後是zmlloc_private_diry,它是查詢系統中proc/self/smap中,表示的是進程fork之後的變化情況,剛fork出來是private_clean的,改變的部分就是private_dirty,在這裡的作用是查詢redis相關程式進程中的子進程記憶體增量
讀Redis學C程式設計二:記憶體配置