redis内存分配分析

简述

redis的内存分配相关的代码存储与zmalloc.hzmalloc.c之中,整体的分配策略非常的简单,需要额外注意HAVE_MALLOC_SIZE这个宏

zmalloc.h

在这里,一开始让我很疑惑的是开头的这一系列条件编译

#if defined(USE_TCMALLOC)
    #define ZMALLOC_LIB ("tcmalloc-" __xstr(TC_VERSION_MAJOR) "." __xstr(TC_VERSION_MINOR))
    #include 
    #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 
    #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 
    #define HAVE_MALLOC_SIZE 1
    #define zmalloc_size(p) malloc_size(p)
#endif

后来认真啃了下网上相关的文章才明白了,redis根据这一系列的条件编译,来决定使用哪种内存分配方案,即tcmalloc,jemalloc,malloc(Apple)。第三个的mallocOS X系统。
可以注意到,在条件编译中, redis均定义了HAVE_MALLOC_SIZE这个宏。原因是,这三种内存分配方案都提供了计算所分配内存大小的函数,如果不使用这三种方案,单纯的使用C自带的malloc的话,需要自己去倒腾计算内存大小的函数。redis将这三种内存分配方案中计算内存大小的函数都换成了统一的zmalloc_size。这也是为了方便以后的使用

zmalloc.c

这里便是内存分配的实现了,花了我好一阵时间去啃,redis确实用到了蛮厉害的技巧。
首先该文件引用了config.hpthread.h俩头文件,config.h包含了一些跨平台的宏,pthread.h则包含了多进程所需锁。

#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这个宏如果被定义过了,说明使用了内存分配方案中有mallloc_size这个函数,能够给出分配的内存大小,否则redis定义了PREFIX_SIZE这一变量,该变量用于在分配内存时,额外的划分一段内存,用于存储本次所分配的内存大小。
其次是used_memory这一静态变量,该变量用于存储进程所占用的内存大小,每次对内存进行增删的时候,都使用宏来修改该值。使用宏而不使用函数,估计是为了增加运行效率。

  • 增加内存时,使用update_zmalloc_stat_alloc来增加used_memory的值
  • 减少内存时,使用update_zmalloc_stat_free来减少used_memory的值

在这俩函数中,需要额外注意的是

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

其中if用于判断本次内存的变化量是否是long类型大小的整数倍,如果不是,则进行一个对齐处理,而后通过update_zmalloc_stat_add安全的增加used_memory的值。

函数实现

redis有俩实现分配内存的函数,分别是zmalloczcalloc,这俩函数看名字就知道区别了。
在分配内存的时候,除了分配参数所要求的大小的内存外,还需额外的分配上文提到的PREFIX_SIZE大小的内存空间,如果定义了HAVE_MALLOC_SIZE
PREFIX_SIZE的值是0,不需要偏移。如果使用的是C自带的malloc,还需要额外的一小步工作

    *((size_t*)ptr) = size;
    update_zmalloc_stat_alloc(size+PREFIX_SIZE);
    return (char*)ptr+PREFIX_SIZE;

通过第一条语句,将指针所指的地方(此时还在开头),存放本次内存分配的大小,而后去更新used_memory。记得返回值要偏移PREFIX_SIZE个字节
如果使用C自带的malloc,则还需要额外的定义一个自己的zmalloc_size
在搞懂上述一个函数之后,其他的函数便不难理解了。
zmalloc_used_memory这个函数用于返回进程所占用的内存大小,要注意到获取used_memory时,都有上一个锁,推测是是为了防止在获取大小的时候,其他进程修改了值。
最后需要注意的是zmalloc_get_rss这个函数用于返回实际占用的物理内存大小,由注释可以看出,这是通过特定的系统调用获取到准确的内存占用。
具体的可以参考这篇博文写的十分清楚。此外zmalloc_get_private_dirty也同样直接参考该博文即可。

你可能感兴趣的:(redis)