ふわふわ時間

0%

Redis 源码之 zmalloc

Redis 内存分配及释放相关源码阅读笔记。


源码文件 /src/zmalloc.h & /src/zmalloc.c

分配内存

zmalloc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void *zmalloc(size_t size) {
// 调用 malloc 分配内存
void *ptr = malloc(size+PREFIX_SIZE);

// 处理异常
if (!ptr) zmalloc_oom_handler(size);
#ifdef HAVE_MALLOC_SIZE
update_zmalloc_stat_alloc(zmalloc_size(ptr));
// HAVE_MALLOC_SIZE 为 1 时,PREFIX_SIZE 为 0,直接返回 ptr
return ptr;
#else
// 保存数据所需分配内存的实际大小
*((size_t*)ptr) = size;
// 记录内存使用情况,更新 used_memory
update_zmalloc_stat_alloc(size+PREFIX_SIZE);
// PREFIX_SIZE 中存储了 malloc size
return (char*)ptr+PREFIX_SIZE;
#endif
}
  1. zmalloc_size

    使用 tcmalloc 1.6+jemalloc 2.1+malloc,则 HAVE_MALLOC_SIZE 为 1 。至于不同内存分配函数的区别,可以参考这篇博客

    自身定义的 zmalloc_size 函数如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    /* Provide zmalloc_size() for systems where this function is not provided by
    * malloc itself, given that in that case we store a header with this
    * information as the first bytes of every allocation. */
    #ifndef HAVE_MALLOC_SIZE
    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;
    }
    size_t zmalloc_usable(void *ptr) {
    return zmalloc_size(ptr)-PREFIX_SIZE;
    }
    #endif

  2. update_zmalloc_stat_alloc

    1
    2
    3
    4
    5
    6
    #define update_zmalloc_stat_alloc(__n) do { \
    size_t _n = (__n); \
    if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \
    // atomicIncr(var,count)
    atomicIncr(used_memory,__n); \
    } while(0)

    根据 redis #4770,第 2、3 两行可以删除,由于并未计算内存补齐的原因,全局变量 used_memory 一般小于实际分配内存大小。

    Tips: do {} while(0) 的作用

    • 辅助定义复杂的宏,避免引用的时候出错
    • 避免使用 goto 对程序流进行统一的控制,例如:使用 break 来代替 goto,后续的处理工作在 while 之后,就能够达到同样的效果
    • 避免空宏引起的 warning
    • 定义一个单独的函数块来实现复杂的操作

    Tips:\(a\) 调整为 \(b = 2^n\) 的整数倍

    1
    2
    if (a & (b-1))
    a += b - (a & (b-1));

zcalloc

源码逻辑和 zmalloc 一样,此处主要介绍 malloccalloc 的区别:

区别 malloc calloc
定义 void* malloc(size_t size); void* calloc(size_t num, size_t size);
作用 分配 size 字节未初始化的内存空间 num 个大小为 size 的对象分配内存空间,并且初始化每一 bit 为 0

两个函数都是线程安全的,并且都自带满足内存对齐,当分配成功时,返回指向初始位置的指针,该指针需要调用 free()realloc() 释放,当分配失败时,返回空指针。

zrealloc

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
void *zrealloc(void *ptr, size_t size) {
#ifndef HAVE_MALLOC_SIZE
void *realptr;
#endif
size_t oldsize;
void *newptr;

// 重新分配的内存的大小为 0
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);

// 记录内存使用情况,更新 used_memory
update_zmalloc_stat_free(oldsize);
update_zmalloc_stat_alloc(zmalloc_size(newptr));
// HAVE_MALLOC_SIZE 为 1 时,PREFIX_SIZE 为 0,直接返回 newptr
return newptr;
#else
realptr = (char*)ptr-PREFIX_SIZE;
oldsize = *((size_t*)realptr); // 对应 *((size_t*)ptr) = size;
newptr = realloc(realptr,size+PREFIX_SIZE);
if (!newptr) zmalloc_oom_handler(size);

*((size_t*)newptr) = size;
// 记录内存使用情况,更新 used_memory
update_zmalloc_stat_free(oldsize+PREFIX_SIZE);
update_zmalloc_stat_alloc(size+PREFIX_SIZE);
// PREFIX_SIZE 中存储了 malloc size
return (char*)newptr+PREFIX_SIZE;
#endif
}
  1. update_zmalloc_stat_free

    1
    2
    3
    4
    5
    6
    #define update_zmalloc_stat_free(__n) do { \
    size_t _n = (__n); \
    if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \
    // atomicDecr(var,count)
    atomicDecr(used_memory,__n); \
    } while(0)

    同上,根据 redis #4770,第 2、3 两行可以删除,由于并未计算内存补齐的原因,全局变量 used_memory 一般小于实际分配内存大小。

  2. 疑问:变量定义语句置于判断返回语句前是否浪费了为变量分配存储空间的时间?变量定义是否应该离使用变量语句近一些更好?

释放内存

zfree

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void zfree(void *ptr) {
#ifndef HAVE_MALLOC_SIZE
void *realptr;
size_t oldsize;
#endif
// 指针为空时无需调用 free
if (ptr == NULL) return;
#ifdef HAVE_MALLOC_SIZE
// 记录内存使用情况,更新 used_memory
update_zmalloc_stat_free(zmalloc_size(ptr));
free(ptr);
#else
realptr = (char*)ptr-PREFIX_SIZE;
oldsize = *((size_t*)realptr);
// 记录内存使用情况,更新 used_memory
update_zmalloc_stat_free(oldsize+PREFIX_SIZE);
free(realptr);
#endif
}

辅助函数

复制字符串

1
2
3
4
5
6
7
8
char *zstrdup(const char *s) {
size_t l = strlen(s)+1; // '\0' 结尾
char *p = zmalloc(l);

// 调用字符串复制函数 void* memcpy(void *dest, const void *src, size_t count);
memcpy(p,s,l);
return p;
}

获取 used_memory

1
2
3
4
5
6
size_t zmalloc_used_memory(void) {
size_t um;
// atomicGet(var,dstvar)
atomicGet(used_memory,um);
return um;
}

设置异常处理函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 默认异常处理函数如下
static void zmalloc_default_oom(size_t size) {
fprintf(stderr, "zmalloc: Out of memory trying to allocate %zu bytes\n",
size);
fflush(stderr);
abort();
}

static void (*zmalloc_oom_handler)(size_t) = zmalloc_default_oom;


// 设置异常处理函数
void zmalloc_set_oom_handler(void (*oom_handler)(size_t)) {
zmalloc_oom_handler = oom_handler;
}