0%

Redis源码-内存分配zmalloc

源码位置:zmalloc.c/zmalloc.h

因为Redis没有自己实现内存池,所以系统内存分配器的性能及碎片率会对redis造成一些性能上的影响。
Redis采用了3种内存分配器:tcmalloc、jemalloc、malloc。
Redis在编译时,首先会判断是否使用tcmalloc,其次是jemalloc,如果都没有使用,则使用libc中的内存管理函数malloc。

功能函数总览


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void *zmalloc(size_t size); // 调用malloc函数申请size大小的内存空间(malloc不初始化,里边的数据是随机的)
void *zcalloc(size_t size); // 调用calloc函数申请size大小的内存空间(calloc系统会自动初始化内存为零)
void *zrealloc(void *ptr, size_t size); // 调用realloc函数重新分配size大小的内存空间
void zfree(void *ptr); // 调用free释放ptr的内存
char *zstrdup(const char *s); // 深拷贝字符串s
size_t zmalloc_used_memory(void); // 获取以分配的内存空间大小
void zmalloc_set_oom_handler(void (*oom_handler)(size_t)); // 可自定义内存溢出的处理方法
size_t zmalloc_get_rss(void); // 获取rss信息(Resident Set Size 常驻内存集)
int zmalloc_get_allocator_info(size_t *allocated, size_t *active, size_t *resident); // 获取jemalloc分配的信息
void set_jemalloc_bg_thread(int enable); // flushdb没有通信后,让jemalloc异步清除
int jemalloc_purge(); // 手动清理内存碎片
size_t zmalloc_get_private_dirty(long pid); // 获取smap中的Private_Dirty大小
size_t zmalloc_get_smap_bytes_by_field(char *field, long pid); // 获取smap中的某字段大小
size_t zmalloc_get_memory_size(void); // 获取物理内存的大小
void zlibc_free(void *ptr); // 释放内存,不更新used_memory(不知道为啥,等以后看懂了再注释)

函数实现


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 记录以使用的内存总大小,对该变量的操作都是原子操作
// 在redis-cli使用info命令可以查看
static size_t used_memory = 0;

// 调用系统函数malloc申请size大小的内存空间,PREFIX_SIZE根据不同平台和HAVE_MALLOC_SIZE控制的。
// 如果分配失败,则调用zmalloc_oom_handler()函数来打印异常,并返回空值。
// 若分配成功,会在update_zmalloc_stat_alloc()宏定义函数中更新used_memory这个静态变量的值
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
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 调用系统函数calloc申请size大小的内存空间,功能函数与zmalloc()相同
// malloc与calloc的区别在于,malloc申请的内存不进行初始化,内存里的值是随机的,calloc申请的内存系统会讲其初始化为零
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
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 根据新的size进行内存分配,并且更新used_memory变量
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
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 释放ptr的内存空间,并更新used_memory变量
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
}

1
2
3
4
5
6
7
8
// 深拷贝字符串s,创建字符串副本
char *zstrdup(const char *s) {
size_t l = strlen(s)+1;
char *p = zmalloc(l);

memcpy(p,s,l);
return p;
}

1
2
3
4
5
6
// 获取used_memory的值
size_t zmalloc_used_memory(void) {
size_t um;
atomicGet(used_memory,um);
return um;
}

1
2
3
4
// 可自定义分配失败时的处理方式
void zmalloc_set_oom_handler(void (*oom_handler)(size_t)) {
zmalloc_oom_handler = oom_handler;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 获取rss信息(Resident Set Size 常驻内存集)
size_t zmalloc_get_rss(void) {
int page = sysconf(_SC_PAGESIZE);
size_t rss;
char buf[4096];
char filename[256];
int fd, count;
char *p, *x;

snprintf(filename,256,"/proc/%d/stat",getpid());
if ((fd = open(filename,O_RDONLY)) == -1) return 0;
if (read(fd,buf,4096) <= 0) {
close(fd);
return 0;
}
close(fd);

p = buf;
count = 23; /* RSS is the 24th field in /proc/<pid>/stat */
while(p && count--) {
p = strchr(p,' ');
if (p) p++;
}
if (!p) return 0;
x = strchr(p,' ');
if (!x) return 0;
*x = '\0';

rss = strtoll(p,NULL,10);
rss *= page;
return rss;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 获取jemalloc分配的信息,在jemalloc前提下使用
int zmalloc_get_allocator_info(size_t *allocated,
size_t *active,
size_t *resident) {
uint64_t epoch = 1;
size_t sz;
*allocated = *resident = *active = 0;
/* Update the statistics cached by mallctl. */
sz = sizeof(epoch);
je_mallctl("epoch", &epoch, &sz, &epoch, sz);
sz = sizeof(size_t);
/* Unlike RSS, this does not include RSS from shared libraries and other non
* heap mappings. */
je_mallctl("stats.resident", resident, &sz, NULL, 0);
/* Unlike resident, this doesn't not include the pages jemalloc reserves
* for re-use (purge will clean that). */
je_mallctl("stats.active", active, &sz, NULL, 0);
/* Unlike zmalloc_used_memory, this matches the stats.resident by taking
* into account all allocations done by this process (not only zmalloc). */
je_mallctl("stats.allocated", allocated, &sz, NULL, 0);
return 1;
}

1
2
3
4
5
6
7
// flushdb没有通信后,让jemalloc异步清除,在jemalloc前提下使用
void set_jemalloc_bg_thread(int enable) {
/* let jemalloc do purging asynchronously, required when there's no traffic
* after flushdb */
char val = !!enable;
je_mallctl("background_thread", NULL, 0, &val, 1);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
// 整理内存碎片,在jemalloc前提下使用
int jemalloc_purge() {
/* return all unused (reserved) pages to the OS */
char tmp[32];
unsigned narenas = 0;
size_t sz = sizeof(unsigned);
if (!je_mallctl("arenas.narenas", &narenas, &sz, NULL, 0)) {
sprintf(tmp, "arena.%d.purge", narenas);
if (!je_mallctl(tmp, NULL, 0, NULL, 0))
return 0;
}
return -1;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 获取smap中的Private_Dirty(映射中已由此进程写入但未被任何其他进程引用的页面)大小
size_t zmalloc_get_private_dirty(long pid) {
return zmalloc_get_smap_bytes_by_field("Private_Dirty:",pid);
}
// 获取进程的smap文件中,某字段字节的大小
size_t zmalloc_get_smap_bytes_by_field(char *field, long pid) {
char line[1024];
size_t bytes = 0;
int flen = strlen(field);
FILE *fp;

if (pid == -1) {
fp = fopen("/proc/self/smaps","r");
} else {
char filename[128];
snprintf(filename,sizeof(filename),"/proc/%ld/smaps",pid);
fp = fopen(filename,"r");
}

if (!fp) return 0;
while(fgets(line,sizeof(line),fp) != NULL) {
if (strncmp(line,field,flen) == 0) {
char *p = strchr(line,'k');
if (p) {
*p = '\0';
bytes += strtol(line+flen,NULL,10) * 1024;
}
}
}
fclose(fp);
return bytes;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 获取物理内存的大小
size_t zmalloc_get_memory_size(void) {
#if defined(__unix__) || defined(__unix) || defined(unix) || \
(defined(__APPLE__) && defined(__MACH__))
#if defined(CTL_HW) && (defined(HW_MEMSIZE) || defined(HW_PHYSMEM64))
int mib[2];
mib[0] = CTL_HW;
#if defined(HW_MEMSIZE)
mib[1] = HW_MEMSIZE; /* OSX. --------------------- */
#elif defined(HW_PHYSMEM64)
mib[1] = HW_PHYSMEM64; /* NetBSD, OpenBSD. --------- */
#endif
int64_t size = 0; /* 64-bit */
size_t len = sizeof(size);
if (sysctl( mib, 2, &size, &len, NULL, 0) == 0)
return (size_t)size;
return 0L; /* Failed? */

#elif defined(_SC_PHYS_PAGES) && defined(_SC_PAGESIZE)
/* FreeBSD, Linux, OpenBSD, and Solaris. -------------------- */
return (size_t)sysconf(_SC_PHYS_PAGES) * (size_t)sysconf(_SC_PAGESIZE);

#elif defined(CTL_HW) && (defined(HW_PHYSMEM) || defined(HW_REALMEM))
/* DragonFly BSD, FreeBSD, NetBSD, OpenBSD, and OSX. -------- */
int mib[2];
mib[0] = CTL_HW;
#if defined(HW_REALMEM)
mib[1] = HW_REALMEM; /* FreeBSD. ----------------- */
#elif defined(HW_PHYSMEM)
mib[1] = HW_PHYSMEM; /* Others. ------------------ */
#endif
unsigned int size = 0; /* 32-bit */
size_t len = sizeof(size);
if (sysctl(mib, 2, &size, &len, NULL, 0) == 0)
return (size_t)size;
return 0L; /* Failed? */
#else
return 0L; /* Unknown method to get the data. */
#endif
#else
return 0L; /* Unknown OS. */
#endif
}

1
2
3
4
// 使用对应的内存分配器的free函数进行内存释放
void zlibc_free(void *ptr) {
free(ptr);
}