Redis是一种使用C语言编写的NoSQL数据库,特点是高性能,高灵活性。但由于C语言没有自带GC,所以Redis在实际使用过程中,内存的分配和释放的合理性和高效性就非常重要。为了达到这个目的,Redis的实现中封装了C里的malloc,calloc,realloc和free函数来对自己的内存进行管理。这些实现都在zmalloc.h和zmalloc.c中。程序中维护一个use_memory,表示已用内存的大小,每当分配或释放内存时都会对其进行更新。
Redis中的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
}
可以看到,zmalloc函数传入一个size变量,表示当前想要分配的内存空间。然后调用封装起来的malloc分配了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
对于不同平台来说,PREFIX_SIZE的值是不同的
- 若系统中存在Google的TC_MALLOC库,则使用tc_malloc一族函数代替原本的malloc一族函数。
- 若当前系统是Mac系统,则使用<malloc/malloc.h>中的内存分配函数。
- 其他情况,则分配一个size_t的大小。
*((size_t*)ptr) = size;
ptr的前size_t个字节空间存放的是size,也就是本次分配空间的大小。
而update_zmalloc_stat_alloc是一个宏(不写成函数的原因可能是因为效率问题)。它的目的是更新used_memory,其定义如下:
#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)
理解这句话是很关键的:
if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1));
说白了它进行了一个内存对齐的过程。因为在操作系统申请内存的时候,往往是对齐的,简单来讲,这样方便数据存放与传输。具体内容可以搜索“内存对齐”。
上面这句话先验证_n是不是sizeof(long)的整数倍,如果不是的话就进行一个向上取整的过程,令_n是sizeof(long)的整数倍。
关于zmalloc和它封装的malloc的关系,可以用下图来说明: