#ifndef __ZMALLOC_H #define __ZMALLOC_H /* Double expansion needed for stringification of macro values. */ #define __xstr(s) __str(s) #define __str(s) #s #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
首先是一些代码的宏定义,其中代码运用了#if defined(X)这样的写法,这样定义的目的是为了判断X是否已经编译过,如果已经在之前编译过,那么对于中间的代码才会执行,如果是没有定义X那么将会忽略中间的代码部分,同时还有另外一种写法,#ifdnf与上面的写法的作用是一样的,同时我们也可以这样写,#if !defined(X),与#ifndef这两者的意思是若之前没有对这个进行编译,那么就执行中间的代码。
后面还有一些定义,我们可以先忽略掉,直接进入与本文相关的定义
void *zmalloc(size_t size); void *zcalloc(size_t size); void *zrealloc(void *ptr, size_t size); void zfree(void *ptr); 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);
在定义中出现了一个新的数据类型size_t,这种数据类型可以简单理解为unsigned int,即这种类型有足够大的大小去存储整数,首先第一个函数就是zmalloc,源代码如下
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 }
这其中size是我们需要的数据大小,而后面的PREFIX_SIZE再不同的环境中有着不一样的大小,这个参数是一个宏定义变量,接下来返回的指针用来判断是否为空,如果返回的是空指针,那么将会跳转到后面的zmalloc_oom_handler函数,这个函数的主要目的是打印相关出错信息,代码如下:
static void (*zmalloc_oom_handler)(size_t) = zmalloc_default_oom; static void zmalloc_default_oom(size_t size) { fprintf(stderr, "zmalloc: Out of memory trying to allocate %zu bytes\n", size); fflush(stderr); abort(); }
这样我们可以十分清楚的看到,其实zmalloc_oom_handler其实已经变成了函数指针,而实际执行的代码是zmalloc_default_oom,其中的内容则是打印出信息,并且终止当前的服务(abort()函数),接下来的函数就是宏定义的函数
,无论是#if还是#else部分都涉及到了一个函数—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)); \ if (zmalloc_thread_safe) { \ update_zmalloc_stat_add(_n); \ } else { \ used_memory += _n; \ } \ } while(0)
同样是一个宏定义编译的代码,这一块的代码十分的考究,我看的时候基本上是看了很多人的分析才能看懂,所以接下来的一些话有的可能是我转过来的,因为我实在是不敢班门弄斧~,首先这段代码里面有一个这样的循环do{...}while(0) 这个部分是十分炫酷的写法,总之一句话概括就是—它能确保宏的行为总是相同的,
如果想看详细的解释,可以移步到这个网址 点击打开链接,然后这段代码就是判断分配的内存空间的大小是不是8的倍数。如果内存大小不是8的倍数,就加上相应的偏移量使之变成8的倍数。_n&7 在功能上等价于 _n%8,不过位操作的效率显然更高。
------------------------------------------摘抄分割线----------------------------------------------
#define update_zmalloc_stat_add(__n) do { \ pthread_mutex_lock(&used_memory_mutex); \ used_memory += (__n); \ pthread_mutex_unlock(&used_memory_mutex); \ } while(0)
接下来的函数包括zcalloc()和zrealloc()两个函数,首先我们能够知道两个函数的原型分别是calloc()与realloc(),接下来看一下这链各个函数与malloc()的区别,首先是calloc()的malloc()的却别是前者在分配内存之后需要将里面的数据初始化,而malloc()是不需要的,也就是说malloc()分配出来的值很有可能是不确定的值,而calloc()则是确定的值,realloc()的作用是重新分配已经分配好的内存,说起来可能比较的拗口,但是根本的作用就是改变原有的内存大小,如果需要添加数据那么realloc()的会先检查尾部是否满足需求,若不满足,那么将会开辟新的内存出来,同样的在redis里面的这三个函数除了调用的不同之外,其余的部分都是一样的逻辑,我就不在重复的去贴代码解释了,
接下来的函数就是与malloc()对应的函数free(),redis的zfree()是用来释放申请的动态内存,首先贴代码如下:
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 }
首先对于宏定义部分可以只关注#else的部分,结合之前的zmalloc()分析,我们可以知道realptr 意味着之前的真正内存地址,之后对于取出真正分配内存的大小oldsize = *((size_t*)realptr);之后运用函数将器释放,我们可以看出,redis的作者在处理这一部分的代码上条理十分的清晰,真的是感觉redis的性能这么好不是白来的。
其他部分的代码相对简单,贴在下面弓大家理解:
size_t zmalloc_get_rss(void) { /* If we can't get the RSS in an OS-specific way for this system just * return the memory usage we estimated in zmalloc().. * * Fragmentation will appear to be always 1 (no fragmentation) * of course... */ return zmalloc_used_memory(); }
size_t zmalloc_used_memory(void) { size_t um; if (zmalloc_thread_safe) { #ifdef HAVE_ATOMIC um = __sync_add_and_fetch(&used_memory, 0); #else pthread_mutex_lock(&used_memory_mutex); um = used_memory; pthread_mutex_unlock(&used_memory_mutex); #endif } else { um = used_memory; } return um; }
void zmalloc_enable_thread_safeness(void) { zmalloc_thread_safe = 1; }