Redis(二):Redis的内存分配

    Redis在编译时便会指定内存分配器;内存分配器可以是 libc jemalloc或者tcmalloc,默认是jemalloc。在Redis中内存分配主要靠 zmalloc 实现,在zmalloc.c文件实现,在这个文件内还存在其他相关的操作函数。

void *zmalloc(size_t size); // 申请内存
void *zcalloc(size_t size); // 申请内存,并初始化为0
void *zrealloc(void *ptr, size_t size); // 重申请内存
void zfree(void *ptr); // 释放内存

1、zmalloc

2、PREFIX_SIZE

3、zcalloc

4、zrealloc

5、zfree 

1、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
}

#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)

static size_t used_memory = 0;

    通过malloc申请内存时,会多申请一个 PREFIX_SIZE 的大小。这里的 PREFIX_SIZE 就是用于保存内存长度的。内存申请后,将内存长度保存进内存的头部 PREFIX_SIZE 个字节的内存中。并通过update_zmalloc_stat_alloc 调整 use_memory 的记录。

if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1));

    这行代码的作用是判断_n是不是sizeof(long)的整数倍,如果不是则将_n加上差值,使其成为整数倍。本质上是为了内存对齐的操作,实现内存的精准计算。我们需要注意的是,这段宏定义的作用只是为了use_memory能够精准计算内存对齐之后的内存长度,而malloc出来的内存已经实现了内存对齐指针对齐。 

2、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

    分析zmalloc函数发现存在HAVE_MALLOC_SIZE。当定义了HAVE_MALLOC_SIZEPREFIX_SIZE的长度就为0。那这个HAVE_MALLOC_SIZE是什么来头?这个答案可以在zmalloc.h找到:

#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

    Redis允许用编译的时候使用第三方的内存分配方案,而这些内存分配方案往往自带存储内存长度的策略,如此一来,Redis就不必自己保存这个长度,反而浪费内存空间。使用第三方内存分配方案,会定义宏定义zmalloc_size用于获取内存长度,比如这里的jemalloc:

#define zmalloc_size(p) je_malloc_usable_size(p)

3、zcalloc

void *zcalloc(size_t size) {
    void *ptr = calloc(1, 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
}

    在zcalloc中,size就已经是真实申请的内存。实现逻辑和zmalloc差不多,唯一差别仅是申请内存使用的calloc,内存会被初始化为0。 

4、zrealloc

void *zrealloc(void *ptr, size_t size) {
#ifndef HAVE_MALLOC_SIZE
    void *realptr;
#endif
    size_t oldsize;
    void *newptr;

    if (size == 0 && ptr != NULL) {
        zfree(ptr);
        return NULL;
    }
    if (ptr == NULL) return zmalloc(size);
#ifdef HAVE_MALLOC_SIZE
    oldsize = zmalloc_size(ptr);
    newptr = realloc(ptr,size);
    if (!newptr) zmalloc_oom_handler(size);

    update_zmalloc_stat_free(oldsize);
    update_zmalloc_stat_alloc(zmalloc_size(newptr));
    return newptr;
#else
    realptr = (char*)ptr-PREFIX_SIZE; // 获取真实地址
    oldsize = *((size_t*)realptr); // 获取内存长度
    newptr = realloc(realptr,size+PREFIX_SIZE);
    if (!newptr) zmalloc_oom_handler(size);

    *((size_t*)newptr) = size;
    update_zmalloc_stat_free(oldsize+PREFIX_SIZE);
    update_zmalloc_stat_alloc(size+PREFIX_SIZE);
    return (char*)newptr+PREFIX_SIZE;
#endif
}

    重分配内存,主要看#else部分。首先通过向低位偏移PREFIX_SIZE个字节位置,拿到真实的内存地址,然后通过真实地址拿到旧内存长度。真实地址,可以通过realloc函数重分配内存,而旧内存长度则参与到use_memory的内存长度计算中。

    update_zmalloc_stat_free的作用和update_zmalloc_stat_alloc相反,是用来对use_memory做减法操作的。

#define update_zmalloc_stat_free(__n) do { \
    size_t _n = (__n); \
    if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \
    atomicDecr(used_memory,__n); \
} while(0)

5、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
}

    zfree的时候获取到真实的地址之后计算use_memory,并释放内存。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值