redisソース分析(一)メモリ管理
一、redisメモリ管理の紹介
redisはメモリベースのkey-valueのデータベースであり、そのメモリ管理は非常に重要であり、異なるプラットフォーム間の差異を遮蔽し、メモリ占有量を統計するために、redisはメモリ分配関数をカプセル化し、プログラムではzmalloc、zfreeの一連の関数を統一的に使用し、その対応するソースコードはsrc/zmalloc.hとsrc/zmalloc.c 2つのファイルの中で、ソースコードはここにあります.
二、redisメモリ管理ソース分析
redisパッケージは、下位プラットフォームの違いを遮断するとともに、関連する関数を自分で実現するのに便利であるため、src/zmalloc.hファイルの関連マクロ定義はredisがどのように下位プラットフォームの違いの遮蔽を実現したかを分析するために、zmalloc.hにおける関連マクロ宣言は以下の通りである.
redisはメモリベースのkey-valueのデータベースであり、そのメモリ管理は非常に重要であり、異なるプラットフォーム間の差異を遮蔽し、メモリ占有量を統計するために、redisはメモリ分配関数をカプセル化し、プログラムではzmalloc、zfreeの一連の関数を統一的に使用し、その対応するソースコードはsrc/zmalloc.hとsrc/zmalloc.c 2つのファイルの中で、ソースコードはここにあります.
二、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
#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
#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
#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系统,则使用
中的内存分配函数。 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システムの場合、ソースコードのPREFIX_に対応するsizeof(long long)定長フィールドレコードが使用されるSIZEマクロ.
PREFIX_SIZEは何に使いますか?
現在のプロセスがどれだけのメモリを消費しているかを統計するために.zmalloc.cには、静的変数があります.この変数は、プロセスが現在使用しているメモリの合計数を記録します.この変数は、メモリを割り当てたり解放したりするたびに更新されます(もちろん、スレッドが安全です).メモリを割り当てるときは、どのくらいのメモリを割り当てるかを指定する必要があります.ただし、メモリを解放する場合、(malloc_size関数が提供されていないメモリライブラリの場合)メモリを解放するポインタを指すことで、解放する空間がどれだけ大きいかはわかりません.このとき、上記のPREFIX_SIZEが機能し,その中に記録された内容によって空間の大きさを得ることができる.(ただし、linuxシステムでは、割り当てメモリ領域のサイズを取得する関数もあります.ここを参照してください).static size_t used_memory = 0;
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は明らかに私たちが言ったように利用したtcです.malloc je_malloc Macなどでmalloc_を提供size関数の場合、割り当てメモリのサイズが統一されたmalloc_を通過することを容易に知ることができます.size関数でいいです.ただしmalloc_は提供されていませんsize機能の関数、redisはどのように処理しますか?上のソースコード#elseの下のコードはその実現であり、その対応するメモリ構造は以下の通りである.#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
memory size
割り当てられたメモリに固定サイズのprefis-size空間を追加し、セグメントのメモリのサイズを記録します.sizeが占めるメモリのサイズは既知で、size_です.tタイプの長さなので、*((size_t*)ptr)=size;を選択します.メモリを割り当てるたびに返される実際のアドレスポインタはmemorysizeへのアドレス((char*)ptr+PREFIX_SIZE; ),このポインタにより、実際のメモリのヘッダアドレスを容易に算出し、メモリを解放することができる.
redisはupdateを通過するzmalloc_stat_alloc(_n,_size)とupdate_zmalloc_stat_free(_n)この2つのマクロは、メモリの割り当てやメモリの解放時にused_を更新する役割を果たします.memory変数.update_zmalloc_stat_allocは次のように定義されています.redisはこの更新操作をマクロの形式に書くのは主に効率的な考慮である.#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)
上のコードでは
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システムでは、pidが現在のプロセス番号である/proc/pid/statファイルシステムを読み込むことで取得できます.読み込まれたのは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