内存分配的源码在zmlloc.c 和zmalloc.h
在zmalloc.h中,定义了如下函数。
-
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_enable_thread_safeness(void);
-
void zmalloc_set_oom_handler(void (*oom_handler)(size_t));
-
float zmalloc_get_fragmentation_ratio(void);
-
size_t zmalloc_get_rss(void);
-
size_t zmalloc_get_private_dirty(void);
- void zlibc_free(void *ptr);
在zmalloc.c中
-
#if defined(USE_TCMALLOC)
-
#define malloc(size) tc_malloc(size)
-
#define calloc(count,size) tc_calloc(count,size)
-
#define realloc(ptr,size) tc_realloc(ptr,size)
-
#define free(ptr) tc_free(ptr)
-
#elif defined(USE_JEMALLOC)
-
#define malloc(size) je_malloc(size)
-
#define calloc(count,size) je_calloc(count,size)
-
#define realloc(ptr,size) je_realloc(ptr,size)
-
#define free(ptr) je_free(ptr)
- #endif
redis内存分配方式有三种:
1、libc 的malloc系列方式
2、google 的tcmalloc
3、jemalloc
tcmallloc和jemalloc性能要比malloc高很多,tcmalloc和jemalloc相比,jemalloc要比tcmalloc高一些。因此,redis默认使用jemalloc。
zmalloc.c中有一个静态变量used_memory用来记录当前分配的内存总大小。 在redis-cli使用info命令看到的used_memory就是这里used_memory的统计出来的。
在zmalloc函数中,实际分配内存长度是,是请求的长度+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
也就是说,redis为了方便内存的管理,在分配一块内存之后,会将这块内存的大小存入内存块的头部。如下图。
real_ptr ret_ptr
| |
V V
-------------------------------------------------------------------------
| size | memory block |
--------------------------------------------------------------------------
real_ptr是redis调用malloc后返回的指针。redis将内存块的大小size存入头部,size所占据的内存大小是已知的,为size_t类型的长度,然后返回ret_ptr。当需要释放内存的时候,ret_ptr被传给内存管理程序。通过ret_ptr,程序可以很容易的算出real_ptr的值,然后将real_ptr传给free释放内存。
值得一提的是zmalloc_get_rss()函数。这个函数用来获取进程的RSS。RSS(Resident Set Size),指实际使用物理内存(包含共享库占用的内存)。在linux系统中,可以通过读取/proc/pid/stat文件获取,pid为当前进程的进程号。读取到的不是byte数,而是内存页数。通过系统调用sysconf(_SC_PAGESIZE)可以获得当前系统的内存页大小。Unix系统貌似可以直接通过task_info直接获取,比linux系统简单的多。
获得进程的RSS后,可以计算目前数据的内存碎片大小,直接用rss除以used_memory。rss包含进程的所有内存使用,包括代码,共享库,堆栈等。但是由于通常情况下redis在内存中数据的量要远远大于这些数据所占用的内存,因此这个简单的计算还是比较准确的。