[redis 源碼走讀] zmalloc

內存管理

redis 內存管理實現,有三種方式:

  1. jemalloc (谷歌)
  2. tcmalloc (facebook)
  3. libc (系統)

其中 jemalloctcmalloc 是第三方的實現,libc 的實現相對簡單,沒有做成一個內存池。沒有像 nginx那樣,有自己的內存管理鏈表。頻繁向內核申請內存不是明智的做法。作者應該是推薦使用 tcmallicjemalloc

// 理解宏對相關庫的引入使用。
#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"
#ifdef __GLIBC__
#include <malloc.h>
#define HAVE_MALLOC_SIZE 1
#define zmalloc_size(p) malloc_usable_size(p)
#endif
#endif

c 語言比較精簡的內存池,可以參考 nginx實現。nginx 這種簡單的鏈式內存池,雖然避免了頻繁從內核分配內存,也容易產生內存碎片。即便是 glibc 的 slab 實現內存管理,也不能很好地解決內存碎片問題。所以內存池就是個複雜的問題。在 redis 上要很好地解決該問題,必然會提高整個項目的複雜度,與其自己造輪子,不如用優秀的第三方庫:tcmalloc, jemalloc


核心接口

  • 內存管理
    如果是 libc 實現的內存管理,內存分配會加一個前綴,保存內存長度。有點像 nginx 的字符串結構。分配內存返回內容指針,釋放內存,指針要從數據部分移動到內存長度部分。
// nginx 字符串結構
typedef struct {
   size_t      len;
   u_char     *data;
} ngx_str_t;
#ifdef HAVE_MALLOC_SIZE
#define PREFIX_SIZE (0)
#else
#if defined(__sun) || defined(__sparc) || defined(__sparc__)
#define PREFIX_SIZE (sizeof(long long))
#else
#define PREFIX_SIZE (sizeof(size_t))
#endif
#endif

// 分配內存
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
}

// 釋放內存
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
}
  • 內存對齊和統計
    used_memory 統計內存使用
    分配內存,內存對齊是爲了提高 cpu 效率。但是 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 進行操作,最後卻保存了 __n 。github 上雖然提出了這個問題,貌似沒有得到解決.

歷史版本 blame
歷史

當前版本 blame
當前


測試

jemalloc, tcmalloc, libc 到底哪個庫比較好用,是馬是驢拉出來溜溜才能知道,要根據線上情況進行評估。

可以用redis-benchmark 壓力測試。


參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章