Redis 源码分析(zmalloc部分)

来源:互联网 发布:centos minimal 桌面 编辑:程序博客网 时间:2024/06/05 01:11

Redis 2.8.24


Redis在这个版本使用三种选择作为allocator,

a) tcmalloc:一种比glibc 2.3更快的malloc实现,由google用于优化C++多线程应用而开发。Redis 需要1.6以上的版本。

b) jemalloc:第一次用在FreeBSD 的allocator,于2005年释出的版本。强调降低碎片化,可扩展的并行支持。Redis需要2.1以上版本。

c) libc:最常使用的libc库。GNU libc,默认使用此allocator。


Redis源码结构比较清晰,其中的内存分配器即是zmalloc部分。写在zmalloc.h 和 zmalloc.c,先看头文件:


#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#elif defined(USE_JEMALLOC)#define ZMALLOC_LIB ("jemalloc-" __xstr(JEMALLOC_VERSION_MAJOR) "." __xstr(JEMALLOC_VERSION_MINOR) "." __xstr(JEMALLOC_VERSION_BUGFIX))#include <jemalloc/jemalloc.h>#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 <malloc/malloc.h>#define HAVE_MALLOC_SIZE 1#define zmalloc_size(p) malloc_size(p)#endif#ifndef ZMALLOC_LIB#define ZMALLOC_LIB "libc"#endif
由宏USE_TCMALLOC,USE_JEMALLOC和__APPLE__控制要编译进Redis的allocator,前两个宏从make 传入,后面一个是操作系统宏,若是Apple,则可以提供一个

malloc_size (),用于查看指针指向内存的大小。此函数在jemalloc和tcmalloc中都有提供,但glibc中不提供此函数,宏HAVE_MALLOC_SIZE即是用于控制此函数。使用glibc的情况下,将不会定义HAVE_MALLOC_SIZE宏,头文件中申明了使用glibc时提供的zmalloc_size ():

#ifndef HAVE_MALLOC_SIZEsize_t zmalloc_size(void *ptr);#endif

接下来看看源文件zmalloc.c;

此文件中定义了三个全局变量:

static size_t used_memory = 0;static int zmalloc_thread_safe = 0;pthread_mutex_t used_memory_mutex = PTHREAD_MUTEX_INITIALIZER;

used_memory:已经申请的内存总字节数。

zmalloc_thread_safe:标识是否是线程安全的,默认为0,不安全。

used_memory_mutex:将used_memory作为临界变量,锁住此变量。

关于HAVE_MALLOC_SIZE,前面已经讲过,使用glibc则没有定义此宏。此时申请的内存块将多出PREFIX_SIZE字节在内存块起始地址处,用于保存内存块大小。随后将内存地址偏移PREFIX_SIZE字节,从此开始即是申请的可使用内存。PREFIX_SIZE 是一个宏,不同操作系统(也可能是处理器)的值略有不同:

#ifdef HAVE_MALLOC_SIZE#define PREFIX_SIZE (0)#else#if defined(__sun) || defined(__sparc) || defined(__sparc__)#define PREFIX_SIZE (sizeof(long long))#else#define PREFIX_SIZE (sizeof(size_t))#endif#endif // HAVE_MALLOC_SIZE
此处的宏__sun 或 __sparc 或 __sparc__ 貌似是标识处理器,或者操作系统。不过最重要的还是查看glibc下的值,sizeof (size_t)。与机器有关,我的机器是64位,这个值应该是一个8bytes 整形。

下面看其他的API:

1、void* zmalloc (size_t size):基本思路刚才已经说过,在申请内存时多申请PREFIX_SIZE个字节用于存储内存块大小,而后把指针偏移PREFIX_SIZE bytes后得到可以使用的内存。

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}
此处的zmalloc_oom_handler () 将abort () 程序,用于处理OOM(out of memory)。其中用到一个update_zmalloc_stat_alloc ()函数,其实是一个宏。tcmalloc和jemalloc因为提供malloc_size (),内存块大小不需要我们记录。所以直接调用__sync_add_and_fetch () 和 __sync_sub_and_fetch () 统计内存块使用情况。先看这两个宏,分别在

update_zmalloc_stat_alloc () 和 update_zmalloc_stat_free () 中调用,用于内存使用情况信息更新。

#define update_zmalloc_stat_add(__n) do { \    pthread_mutex_lock(&used_memory_mutex); \    used_memory += (__n); \    pthread_mutex_unlock(&used_memory_mutex); \} while(0)#define update_zmalloc_stat_sub(__n) do { \    pthread_mutex_lock(&used_memory_mutex); \    used_memory -= (__n); \    pthread_mutex_unlock(&used_memory_mutex); \} while(0)
代码很简单,lock 临界区,累加,unlock。以下是两个状态更新函数:

#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)#define 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) { \        update_zmalloc_stat_sub(_n); \    } else { \        used_memory -= _n; \    } \} while(0)
这里使用了一个优化,用于机器字对齐,使内存访问更快。_n & (sizeof (long) - 1)是否能被8 或者4整除(与机器有关)。若非0,则不能整除,加上剩余字节数使之能整除。

举个例子:x64机器上,申请20bytes, 20 & (sizeof (long) - 1) = 20 & 7 = 4,非零,不能整除,即缺 8 - 4 = 4 bytes,于是申请的20 bytes多申请4bytes,24 mod 8 = 0正好。

注:默认此处的zmalloc_thread_safe为0,即是运行在单线程下,不用加锁,直接在used_memory 累加累减即可。

2、 void zfree (void* ptr):在使用jemalloc 和 tcmalloc时,内存申请时的长度不加上PREFIX_SIZE,直接free ()即可,而glibc 要将指针偏移回PREFIX_SIZE,再调用 free ():

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}

3、 size_t zmalloc_size (void* ptr):这个函数是为glibc定制的,只有用这个库时,才能使用这个函数:

#ifndef HAVE_MALLOC_SIZEsize_t zmalloc_size(void *ptr) {    void *realptr = (char*)ptr-PREFIX_SIZE;    size_t size = *((size_t*)realptr);    /* Assume at least that all the allocations are padded at sizeof(long) by     * the underlying allocator. */    if (size&(sizeof(long)-1)) size += sizeof(long)-(size&(sizeof(long)-1));    return size+PREFIX_SIZE;}#endif
原理即时将指针偏移PREFIX_SIZE ,得到块大小,再加上PREFIX_SIZE长度即得到真实的内存大小。


其他还有好几个方法,实现都比较简单,不再熬述。

0 0
原创粉丝点击