从零学习redis(15)--- 源码阅读之内存分配
redis 刘宇帅 5年前 阅读量: 2063
redis 中内存管理没有直接使用 C 语言提供的方法而是做了一个可管理已分配内存大小及添加了自己分配策略的实现,下面从 SDS 开始去了解 redis 的内存管理实现及策略。
SDS 内存管理函数定义如下:
src/sdsalloc.h
#include "zmalloc.h"
#define s_malloc zmalloc
#define s_realloc zrealloc
#define s_free zfree
s_malloc 内存申请、s_realloc 内存重新分配、s_free 内存释放,这里定义分别是 zmalloc.h 里三个函数的别名,而 zmalloc zrealloc zfree 的定义 在 zmalloc.h 中,它们会根据不同的编译参数来决定其具体使用的内存管理库。
src/sdsalloc.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
#ifndef ZMALLOC_LIB
#define ZMALLOC_LIB "libc"
#ifdef __GLIBC__
#include <malloc.h>
#define HAVE_MALLOC_SIZE 1
#define zmalloc_size(p) malloc_usable_size(p)
#endif
#endif
上面的的预处理是 Redis 会根据平台或者依赖的库来决定内存分配方案,包括以下几种:
- google/tcmalloc
- jemalloc/jemalloc
- malloc/malloc
- libc
libc 是 C 语言默认的内存管理库,但是由于 libc 内存碎片率较高,其他三个库在这方面做了优化,另外 redis 在 linux 系统下默认使用 jemalloc 库,因为经验证 jemalloc 比 libc 的碎片率要低很多。
Redis 封装的内存管理函数如下:
void *zmalloc(size_t size); // 申请内存空间
void *zcalloc(size_t size); // 申请并初始化内存
void *zrealloc(void *ptr, size_t size); // 重新申请内存空间
void zfree(void *ptr); // 释放内存
char *zstrdup(const char *s); // 复制字符串
size_t zmalloc_used_memory(void); // 获得程序申请内存的大小(即通过封装的内存管理函数)
void zmalloc_set_oom_handler(void (*oom_handler)(size_t)); // 设置内存不足申请失败时的回调函数
size_t zmalloc_get_rss(void); // 获得程序实际的内存占用(全部内存占用)
int zmalloc_get_allocator_info(size_t *allocated, size_t *active, size_t *resident); // 获得分配器的信息,使用jemalloc 时可获得jemalloc的分配信息
size_t zmalloc_get_private_dirty(long pid); //获得Rss中已改写的私有页面页面大小
size_t zmalloc_get_smap_bytes_by_field(char *field, long pid);//获得/proc/pid/smaps中某一个field的字节大小
size_t zmalloc_get_memory_size(void); // 获得物理内存的大小
void zlibc_free(void *ptr); // libc free 的封装
这里主要看下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
}
这段函数里会根据HAVE_MALLOC_SIZE是否定义走两条分支,这个常量是否标识是否记录分配内存大小,除了 libc 其他三个库都提供了获得内存占用的函数(上面 sdsalloc.h 代码中 zmalloc_size 对应的别名函数)而 libc 需要自己实现,Redis 针对 libc 代码实现如下:
src/zmalloc.c
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;
}
根据上面代码我们可以看到zmalloc_size的实现也比较简单,就是在申请内存的时候在头部多申请一块 PREFIX_SIZE 的空间用来保存。