redisソース解析のメモリ管理

14495 ワード

 


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]は、割り当てられた長さ16 bytesのメモリブロックの個数を表す
.zmalloc.cに静的変数used_がありますmemoryは、現在割り当てられているメモリの合計サイズを記録するために使用されます.
.
コードの解析を開始します.
まず定義マクロPREFIX_SIZE:
1 #ifdef HAVE_MALLOC_SIZE

2  #define PREFIX_SIZE (0)

3  #else

4  #if defined(__sun)

5  #define PREFIX_SIZE (sizeof(long long))

6  #else

7  #define PREFIX_SIZE (sizeof(size_t))

8  #endif

9  #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 because

3 * this expects a different allocation scheme. Therefore, *exclusively* use

4 * 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 >= 6

8 #define HAVE_MALLOC_SIZE 1

9 #define redis_malloc_size(p) tc_malloc_size(p)

10 #endif

11 #elif defined(__APPLE__)

12 #include <malloc/malloc.h>

13 #define HAVE_MALLOC_SIZE 1

14 #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はシステムから実際に得られたメモリサイズであり、2番目のパラメータはプログラム要求のメモリサイズである
.update_zmalloc_stat_allocはまずプログラム要求のメモリサイズがzmalloc_であると判断するallocations配列の対応する下付き
.メモリサイズがzmallocより大きい場合allocations配列の長さ−1では、対応する下付き文字が最後になります.次に、実際に割り当てられたメモリサイズをlongタイプの長さの整数倍に整列します(mallocは通常、整列の問題を考慮し、実際に割り当てられたメモリサイズも整列によって異なります.後述します)
.最後にused_memoryは、zmalloc_で実際に割り当てられたサイズを記録します.allocations対応位置プラス1
.ここでスレッドが安全に設定されている場合は、記録する前に2つの静的変数をロックします.
.
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関数は、前の2つのマクロを直接呼び出すので、何も言うことはありません.
.malloc_がなければsize関数は、割り当てられたメモリヘッダのPREFIX_SIZEサイズの領域には、メモリブロックのサイズが記録される
.コードは簡単です.
1 *((size_t*)ptr) = size;
ptrをsize_に変換tタイプのポインタは、sizeの値を指向するメモリに割り当てます.
.筆者は前にPREFIXを定義する必要はないと感じた.SIZEの場合はシステムを区別しますが、ここではメモリサイズを直接ハードコーディングしたタイプがsize_t
.前のマクロの判断は少し多いです.
.ここでsizeは、返されるメモリ領域のサイズであり、保存サイズのヘッダは含まれません.
メモリブロックサイズを読み込むには2つのステップが必要です.
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システムでは、pidが現在のプロセス番号である/proc/pid/statファイルを読み込むことで取得できます.
.読み込まれたのはbyte数ではなく、メモリページ数です.システム呼び出しsysconf(_SC_PAGESIZE)により、現在のシステムのメモリページサイズを取得できます.
.Unixシステムはtaskを直接通過できるようです.infoは直接取得し、linuxシステムよりずっと簡単です.
プロセスのRSSを取得すると、現在のデータのメモリフラグメントサイズを計算し、rssをused_で直接除算できます.memory
.rssには、コード、共有ライブラリ、スタックなど、プロセスのすべてのメモリ使用が含まれます.しかし、通常、redisのメモリ内のデータの量は、これらのデータが消費するメモリよりもはるかに大きいため、この簡単な計算は比較的正確である.
.
ここで問題があります.プログラムはどれだけのメモリでどれだけのメモリを割り当てているのか、どこからのメモリの破片なのか.実は、mallocが呼び出されると、mallocはパラメータの値に厳密に従ってメモリを割り当てるわけではありません.
.たとえば、プログラムは1つのbyteのメモリだけを要求し、mallocは1つのbyteだけを割り当てることはありません.通常、メモリの位置合わせなどの考慮に基づいて、mallocは4つのbyteを割り当てます.
.このように、プログラムに1 byteメモリが大量に要求される場合、実際には要求の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上向きに整列すると,下位2桁とも0になる.割り当てられたスペースとメモリの位置合わせ