亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

redis 源代碼分析(一) 內存管理

系統 3873 0

一,redis內存管理介紹

? redis是一個基于內存的key-value的數據庫,其內存管理是很重要的,為了屏蔽不同平臺之間的差異,以及統計內存占用量等,redis對內存分配函數進行了一層封裝,程序中統一使用zmalloc,zfree一系列函數,其相應的源代碼在 src/zmalloc.h src/zmalloc.c 兩個文件里,源代碼點 這里

二,redis內存管理源代碼分析

redis封裝是為了屏蔽底層平臺的差異,同一時候方便自己實現相關的函數,我們能夠通過 src/zmalloc.h 文件里的相關宏定義來分析redis是怎么實現底層平臺差異的屏蔽的, 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

...
#ifndef HAVE_MALLOC_SIZE
size_t zmalloc_size(void *ptr);
#endif


    

    

通過上面的宏的預處理我們能夠發現redis為了屏蔽不同系統(庫)的差異進行了例如以下預處理:

A ,若系統中存在Google的TC_MALLOC庫,則使用tc_malloc一族函數取代原本的malloc一族函數。

B ,若系統中存在FaceBook的JEMALLOC庫,則使用je_malloc一族函數取代原本的malloc一族函數。 ??

C ,若當前系統是Mac系統,則使用<malloc/malloc.h>中的內存分配函數。???

D ,其它情況,在每一段分配好的空間前頭,同一時候多分配一個定長的字段,用來記錄分配的空間大小。?

tc_malloc是google開源處理的一套內存管理庫,是用C++實現的,主頁在 這里 。TCMalloc給每一個線程分配了一個線程局部緩存。小分配能夠直接由線程局部緩存來滿足。須要的話,會將對象從中央數據結構移動到線程局部緩存中,同一時候定期的垃圾收集將用于把內存從線程局部緩存遷移回中央數據結構中。這篇 文章 里對TCMalloc有個具體的介紹。

jemalloc 也是一個內存創管理庫,其創始人Jason Evans也是在FreeBSD非常有名的開發者 ,參見 這里 。Jemalloc聚集了malloc的使用過程中所驗證的非常多技術。忽略細節,從架構著眼,最出色的部分仍是arena和thread cache。

讀者一定會有疑問系統不是有了malloc 嗎,為什么還有這種內存管理庫?? 因為經典的libc的分配器碎片率為較高,能夠查看 這篇文章 的分析,關于內存碎片不太了解的童鞋請參考 這里 ,?malloc 和free 怎么工作的參考 這里 。 關于ptmalloc,tcmalloc和jemalloc內存分配策略的一篇總結不錯的文章,請點 這里

以下介紹redis封裝的內存管理相關函數, src/zmalloc.h 有相關聲明。

      void *zmalloc(size_t size);//malloc
void *zcalloc(size_t size);//calloc
void *zrealloc(void *ptr, size_t size);/realloc
void zfree(void *ptr);//free
char *zstrdup(const char *s);
size_t zmalloc_used_memory(void);
void zmalloc_enable_thread_safeness(void);
void zmalloc_set_oom_handler(void (*oom_handler)(size_t));
float zmalloc_get_fragmentation_ratio(void);
size_t zmalloc_get_rss(void);
size_t zmalloc_get_private_dirty(void);
void zlibc_free(void *ptr);
    

如今主要介紹下redis內存分配函數 void *zmalloc(size_t size),其相應的聲明形式例如以下:

      void *zmalloc(size_t size) {
    void *ptr = malloc(size+PREFIX_SIZE);

    if (!ptr) zmalloc_oom_handler(size);
#ifdef HAVE_MALLOC_SIZE
    update_zmalloc_stat_alloc(zmalloc_size(ptr));
    return ptr;
#else
    *((size_t*)ptr) = size;
    update_zmalloc_stat_alloc(size+PREFIX_SIZE);
    return (char*)ptr+PREFIX_SIZE;
#endif
}
    

閱讀源代碼我們發現有個PREFIX_SIZE 宏,其宏定義形式例如以下:

      /* zmalloc.c */
#ifdef HAVE_MALLOC_SIZE
#define PREFIX_SIZE (0)       
#else  
#if defined(__sun)
#define PREFIX_SIZE (sizeof(long long))
#else                         
#define PREFIX_SIZE (sizeof(size_t))
#endif
#endif
    

結合 src/zmalloc.h 有相關宏聲明,我們發現,由于 tc_malloc 、je_malloc 和 Mac平臺下的 malloc 函數族提供了計算已分配空間大小的函數(各自是tc_malloc_size, je_malloc_usable_size和malloc_size),所以就不須要單獨分配一段空間記錄大小了。在linux和sun平臺則要記錄分配空間大小。對于linux,使用sizeof(size_t)定長字段記錄;對于sun 系統,使用sizeof(long long)定長字段記錄,其相應源代碼中的 PREFIX_SIZE 宏。

PREFIX_SIZE 有什么用呢?

為了統計當前進程究竟占用了多少內存。在 zmalloc.c 中,有一個靜態變量:

      static size_t used_memory = 0;
    
這個變量它記錄了進程當前占用的內存總數。每當要分配內存或是釋放內存的時候,都要更新這個變量(當然能夠是線程安全的)。由于分配內存的時候,須要指定分配多少內存。可是釋放內存的時候,(對于未提供malloc_size函數的內存庫)通過指向要釋放內存的指針是不能知道釋放的空間究竟有多大的。這時候,上面提到的PREFIX_SIZE就起作用了,能夠通過當中記錄的內容得到空間的大小。(只是在linux系統上也有對應的函數獲得分配內存空間的大小,參見 這里 )。

通過zmalloc的源代碼我們能夠發現,其分配空間代碼為void *ptr = malloc(size+PREFIX_SIZE); 顯然其分配空間大小為:size+PREFIX_SIZE ,對于使用tc_malloc或je_malloc的情況或mac系統,其 PREFIX_SIZE 為0。當分配失敗時有對應的出錯處理 。

前面我們已經說過redis通過使用used_memory 的變量來統計當前進程究竟占用了多少內存,因此在分配和釋放內存時我們須要緊接著更新used_memory 的相應值,相應到redis源代碼中為:

      #ifdef HAVE_MALLOC_SIZE
    update_zmalloc_stat_alloc(zmalloc_size(ptr));
    return ptr;
#else
    *((size_t*)ptr) = size;
    update_zmalloc_stat_alloc(size+PREFIX_SIZE);
    return (char*)ptr+PREFIX_SIZE;
#endif
    
上面的代碼有事宏預處理 #ifdef HAVE_MALLOC_SIZE 顯然是上面我們說過的利用的tc_malloc je_malloc Mac等提供malloc_size函數的情形,我們能夠非常easy得知分配內存的大小通過統一化的malloc_size函數就可以。可是對于沒有提供malloc_size功能的函數,redis是怎么處理的呢?看上面的源代碼 #else以下的代碼即是事實上現,其相應的內存結構例如以下:
prefix-size memory size
分配的內存前加一個固定大小的prefis-size空間,用于記錄該段內存的大小,size所占領的內存大小是已知的,為size_t類型的長度,因此通過*((size_t*)ptr) = size; 就可以對當前內存塊大小進行指定。每次分配內存后,返回的實際地址指針為指向memorysize的地址( (char*)ptr+PREFIX_SIZE; ),通過該指針,能夠非常easy的計算出實際內存的頭地址,從而釋放內存。

redis通過update_zmalloc_stat_alloc(__n,__size) 和 update_zmalloc_stat_free(__n) 這兩個宏負責在分配內存或是釋放內存的時候更新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)); \ 
    if (zmalloc_thread_safe) { \
        update_zmalloc_stat_add(_n); \
    } else { \
        used_memory += _n; \
    } \
} while(0)
    
redis把這個更新操作寫成宏的形式主要是處于效率的考慮。

上面的代碼中?

A,if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1));
? 主要是考慮對齊問題,保證新增的_n 是 sizeof(long)的倍數。

B, ? if (zmalloc_thread_safe) { \
??????? update_zmalloc_stat_add(_n); \

?????? }

假設進程中有多個線程存在,并保證線程安全zmalloc_thread_safe,則在更新變量的時候要加鎖。? 通過宏HAVE_ATOMIC選擇對應的同步機制。

zmalloc_calloc、zmalloc_free等的實現就不細致介紹了詳情參見 源代碼

最后解說下 zmalloc_get_rss() 函數。
?? 這個函數用來獲取進程的RSS。神馬是RSS?全稱為Resident Set Size,指實際使用物理內存(包括共享庫占用的內存)。在linux系統中,能夠通過讀取/proc/pid/stat文件系統獲取,pid為當前進程的進程號。讀取到的不是byte數,而是內存頁數。通過系統調用sysconf(_SC_PAGESIZE)能夠獲得當前系統的內存頁大小。 獲得進程的RSS后,能夠計算眼下數據的內存碎片大小,直接用rss除以used_memory。rss包括進程的全部內存使用,包括代碼,共享庫,堆棧等。 哪來的內存碎片?上面我們已經說明了通常考慮到效率,往往有內存對齊等方面的考慮,所以,碎片就在這里產生了。相比傳統glibc中的malloc的內存利用率不是非常高通常會使用別的內存庫系統。在redis中默認的已經不使用簡單的malloc了而是使用 jemalloc, 在源文件src/Makefile下有這樣一段代碼:

      
ifeq ($(uname_S),Linux)
MALLOC = jemalloc
能夠知道在linux系統上默認使用jemalloc, 在redis公布的源代碼中有相關的庫 deps/jemalloc


總的來說 redis則全然自主分配內存,在請求到的時候實時依據內建的算法分配內存,全然自主控制內存的管理。簡單即是美吧,只是功能確實強大。


參考:

http://blog.ddup.us/?p=136

redis 源代碼分析(一) 內存管理


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 最近免费中文字幕大全免费版视频 | 国产精品一区二区久久精品涩爱 | 最近中文字幕无免费视频 | 99手机在线视频 | 久久久精品久久视频只有精品 | 欧美日韩中文亚洲v在线综合 | 香蕉视频网站在线播放 | 亚洲婷婷网 | 亚洲精品一区 | 99综合在线 | 99久久综合狠狠综合久久 | 欧美白人猛性xxxxx交69 | 四虎免费永久网站入口 | 亚洲国产二区三区久久 | www四虎影院 | 日不卡在线| 99re这里只有精品99 | 欧美做爰xxxⅹ在线视频hd | 玖玖国产在线观看 | 狠狠色伊人亚洲综合成人 | 久久久久久亚洲精品影院 | 亚洲精品美女久久久久网站 | 99热在线观看| 就操视频| 亚洲综合图区 | 日日噜噜噜夜夜爽爽狠狠69 | 国产在线观看不卡 | 中文字幕福利视频 | 91成人国产网站在线观看 | 欧美精品在线一区 | 免费视频一区 | 亚洲精品一区二区三区中文字幕 | 60岁妇女毛片 | 欧美伊人久久大香线蕉综合69 | 四虎影视网站 | 人人干人 | 久久香蕉国产线看观看精品yw | 色网站视频 | 欧美日韩中文在线视频 | 国产毛片一区二区三区精品 | 日本免费人做人一区在线观看 |