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万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章