Redis zmalloc



#
運算符用於創建字符串,#運算符後面應該跟一個形參(中間可以有空格或Tab),例如:

#define STR(s) # s
STR(hello 	world)

cpp命令預處理之後是"hello␣world",自動用"號把實參括起來成爲一個字符串,並且實參中的連續多個空白字符被替換成一個空格。

再比如:

#define STR(s) #s
fputs(STR(strncmp("ab\"c\0d", "abc", '\4"')
	== 0) STR(: @\n), s);

預處理之後是fputs("strncmp(\"ab\\\"c\\0d\", \"abc\", '\\4\"') == 0" ": @\n", s);,注意如果實參中包含字符常量或字符串,則宏展開之後字符串的界定符"要替換成\",字符常量或字符串中的\"字符要替換成\\\"

在宏定義中可以用##運算符把前後兩個預處理Token連接成一個預處理Token,和#運算符不同,##運算符不僅限於函數式宏定義,變量式宏定義也可以用。例如:

#define CONCAT(a, b) a##b
CONCAT(con, cat)

預處理之後是concat。再比如,要定義一個宏展開成兩個#號,可以這樣定義:

#define HASH_HASH # ## #

中間的##是運算符,宏展開時前後兩個#號被這個運算符連接在一起。注意中間的兩個空格是不可少的,如果寫成####,會被劃分成####兩個Token,而根據定義##運算符用於連接前後兩個預處理Token,不能出現在宏定義的開頭或末尾,所以會報錯。



首先,在C語言的宏中是容許嵌套的,其嵌套後,一般的展開規律像函數的參數一樣,先展開參數,在分析函數,所以展開順序是由內而外,但是當宏中有#則不再展開參數了,如果宏中有##,則先展開函數,再展開裏面的參數。如下面的例子:#include <stdio.h>

#define TO_STRING2( x ) #x
#define TO_STRING( x ) TO_STRING1( x )
#define TO_STRING1( x ) #x
#define PARAM( x ) #x
#define ADDPARAM( x ) INT_##x
int main()
{
    const char * str = TO_STRING(PARAM( ADDPARAM( 1 ) ) );
    printf("%s\n",str);
    str = TO_STRING2(PARAM( ADDPARAM( 1 ) ) );
    printf("%s\n",str);
    return 1;
}它的輸出結果爲:
"ADDPARAM( 1 )"
PARAM( ADDPARAM( 1 ) )
對於宏TO_STRING,它的定義中沒有#,所以先展開裏面的“PARAM( ADDPARAM( 1 ) )”,由於PARAM中有#,所以裏面展開的結果爲ADDPARAM( 1 ),然後外面再展開,其結果爲"ADDPARAM( 1 )"
而對於TO_STRING2,其定義中有#,所以直接展開,其結果爲PARAM( ADDPARAM( 1 ) )
而對於下面的例子:
#include <stdio.h>
#define TO_STRING2( x ) a_##x
#define TO_STRING( x ) TO_STRING1( x )
#define TO_STRING1( x ) #x
#define PARAM( x ) #x
#define ADDPARAM( x ) INT_##x
int main()
{
    str = TO_STRING(TO_STRING2(PARAM( ADDPARAM( 1 ) ) ));
    printf("%s\n",str);
    return 1;
}
其輸出結果爲:
a_PARAM( INT_1 )

因爲首先分析TO_STRING的參數TO_STRING2(PARAM( ADDPARAM( 1 ) ) ),而對於TO_STRING2(x),由於其定義中有##,所以先展開該函數,其結果爲a_PARAM(ADDPARAM( 1 )),而ADDPARAM( 1 )展開,結果爲INT_1,所以其總結果爲a_PARAM( INT_1 ) 

HAVE_MALLOC_SIZE用來確定系統是否有函數malloc_size。

redis_malloc_size的功能是獲得參數p所指向的內存塊的大小。但libc的free確實可以知道ptr的實際大小,但我們的程序不知道,只能根據指針指向的頭幾個B,來確定。

void * calloc ( size_t num, size_t size ); // zcalloc(size_t size);不用數組的概念,僅用來創建初始爲0的內存空間。

Allocate space for array in memory

Allocates a block of memory for an array of num elements, each of them size bytes long, and initializes all its bits to zero. //malloc 並沒有清0

The effective result is the allocation of an zero-initialized memory block of (num * size) bytes.
void * realloc ( void * ptr, size_t size );

Reallocate memory block

The size of the memory block pointed to by the ptr parameter is changed to the size bytes, expanding or reducing the amount of memory available in the block.

The function may move the memory block to a new location, in which case the new location is returned. The content of the memory block is preserved up to the lesser of the new and old sizes(保持原有大小,並不因爲參數變小而縮減size大小,即使block已經移動了,即realloc不進行裁剪), even if the block is moved. If the new size is larger, the value of the newly allocated portion is indeterminate.
 如果新分配的內存比原來的大,則原來的數據保持不變.增加的空間不進行初始化.如果新分配的內存比原來的內存小,則新的內存空間不被初始化.realloc函數返回指向新分配空間的指針.若無法滿足要求則返回NULL 指針.在這種情況下.原指針p指向的單元內容保持不變.


In case that ptr is NULL, the function behaves exactly as malloc, assigning a new block of size bytes and returning a pointer to the beginning of it.

In case that the size is 0, the memory previously allocated in ptr is deallocated as if a call to free was made, and a NULL pointer is returned.

Redis中到處都會進行內存分配操作。爲了屏蔽不同平臺之間的差異,以及統計內存佔用量等,Redis對內存分配函數進行了一層封裝,程序中統一使用zmalloc,zfree一系列函數,位於zmalloc.h,zmalloc.c文中。

上邊說過,封裝就是爲了屏蔽底層平臺的差異,同時方便自己實現相關的統計函數。具體來說就是:

  • 若系統中存在Google的TC_MALLOC庫,則使用tc_malloc一族函數代替原本的malloc一族函數。
  • 若當前系統是Mac系統,則使用<malloc/malloc.h>中的內存分配函數。
  • 其他情況,在每一段分配好的空間前頭,同時多分配一個定長的字段,用來記錄分配的空間大小。
因爲 tc_malloc 和 Mac平臺下的 malloc 函數族提供了計算已分配空間大小的函數(分別是tc_malloc_size和malloc_size),所以就不需要單獨分配一段空間記錄大小了。而針對linux和sun平臺則要記錄分配空間大小。對於linux,使用sizeof(size_t)定長字段記錄;對於sun os,使用sizeof(long long)定長字段記錄。也就是上邊源碼中的 PREFIX_SIZE 宏。
size_t是標準C庫中定義的,應爲unsigned int。(隨OS位數不同而不同),用於在申請時,多申請一個PREFIX空間大小存放申請的大小,申請空間的頭PREFIX大小存放申請量,ptr對外指向PREFIX後面的地址。

那麼這個記錄有什麼用呢?答案是,爲了統計當前進程到底佔用了多少內存。在 zmalloc.c 中,有這樣一個靜態變量:

static size_t used_memory = 0;

它記錄了進程當前佔用的內存總數。每當要分配內存或是釋放內存的時候,都要更新這個變量。因爲分配內存的時候,可以明確知道要分配多少內存。但是釋放內存的時候,(對於未提供malloc_size函數的平臺)僅通過指向要釋放內存的指針是不能知道釋放的空間到底有多大的。這個時候,上邊提到的PREFIX_SIZE定長字段就起作用了,可以通過其中記錄的內容得到空間的大小。zmalloc函數如下(去掉無關代碼):

互斥鎖pthread_mutex_t的使用,

在LinuxThreads實現中,pthread_mutex_t是一個結構,而PTHREAD_MUTEX_INITIALIZER則是一個結構常量。


update_zmalloc_stat_alloc(__n,__size) 和 update_zmalloc_stat_free(__n) 這兩個宏負責在分配內存或是釋放內存的時候更新used_memory變量。定義成宏主要是出於效率上的考慮。將其還原爲函數,就是下邊這個樣子:

void 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) {
            pthread_mutex_lock(&used_memory_mutex);
            used_memory -= _n;
            pthread_mutex_unlock(&used_memory_mutex);
        } else {
            used_memory -= _n;
        }
    } while(0)
}

代碼中除了更新used_memory變量外,還有幾個要關注的地方:

  1. 先對_n的低位向上取整,最後_n變爲sizeof(long)的倍數,比如對於32位系統,sizeof(long) == 100(二進制),_n向上取整之後,低兩位都變爲0。
  爲什麼要內存對齊,因爲實際的申請量中,OS往往是內存對齊的。
內存對齊的好處:
1,節省地址線
2,cache優化。(4B的整數倍)
  1. 如果進程中有多個線程存在,則在更新變量的時候要加鎖。//後臺線程可能要修改,有多少個後臺線程。
  2. 在zmalloc函數中還有一個統計量要更新:zmalloc_allocations[ ]。

另一個對內存使用量的統計通過調用 zmalloc_used_memory 函數返回:

size_t zmalloc_used_memory(void) {
    size_t um;

    if (zmalloc_thread_safe) pthread_mutex_lock(&used_memory_mutex);
    um = used_memory; // used_memory 無論讀寫都要加鎖。
    if (zmalloc_thread_safe) pthread_mutex_unlock(&used_memory_mutex);
    return um;
}

另外 zmalloc.c 中,還針對不同的系統實現了 zmalloc_get_rss 函數,在linux系統中是通過讀取/proc/$pid/stat文件獲得系統統計的內存佔用量。關於/proc虛擬文件系統可以看之前的文章


RSS 返回
如果不能通過OS來返回,只能返回used_momory_mutex來估測。
 /* Fragmentation = RSS / allocated-bytes */  返回碎片比=RSS/有效數據。
     70 float zmalloc_get_fragmentation_ratio(void) {
     71     return (float)zmalloc_get_rss()/zmalloc_used_memory(); 所謂used_memory,redis統一用zmalloc,zfree進行統計的,堆上的內存。
     72 }
      1  /* WARNING: the function zmalloc_get_rss() is not designed to be fast
      2  * and may not be called in the busy loops where Redis tries to release
      3  * memory expiring or swapping out objects.
      4  *
      5  * For this kind of "fast RSS reporting" usages use instead the
      6  * function RedisEstimateRSS() that is a much faster (and less precise)
      7  * version of the funciton. */
     12 size_t zmalloc_get_rss(void) {
     13     int page = sysconf(_SC_PAGESIZE);
     14     size_t rss;
     15     char buf[4096];
     16     char filename[256];
     17     int fd, count;
     18     char *p, *x;
     19     snprintf(filename,256,"/proc/%d/stat",getpid()); //獲得文件名
     20     if ((fd = open(filename,O_RDONLY)) == -1) return 0;
     21     if (read(fd,buf,4096) <= 0) { //讀入數據
     22         close(fd);
     23         return 0;
     24     }
     25     close(fd);
     26     p = buf;
     27     count = 23; /* RSS is the 24th field in /proc/<pid>/stat */
         /*
               

Locate first occurrence of character in string

Returns a pointer to the first occurrence of character in the C string str. The terminating null-character is considered part of the C string. Therefore, it can also be located to retrieve a pointer to the end of a string. 28 while(p && count--) { 29 p = strchr(p,' '); 30 if (p) p++; 31 } 32 if (!p) return 0;//23次退出 33 x = strchr(p,' '); // 34 if (!x) return 0; 35 *x = '\0';修改結尾, 36 rss = strtoll(p,NULL,10); //轉化爲long long 類型,10進制? 37 rss *= page;//乘上頁大小。 38 return rss; 39 }
發佈了29 篇原創文章 · 獲贊 4 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章