Redis 源码分析(zmalloc部分)

30 篇文章 0 订阅
12 篇文章 0 订阅

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_SIZE
size_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_SIZE
size_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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值