Redis 内存分配及释放相关源码阅读笔记,源码文件 zmalloc.h & zmalloc.c


1. 小结

  • 封装 tcmalloc, jemalloc, libmalloc, ptmalloc 或自定义内存管理,增加内存使用量统计,但由于并未计算内存补齐的原因,全局变量 used_memory 一般小于实际分配内存大小
  • 在使用针对 Redis 修改的特殊 jemalloc 时,才能启用 Redis 内存碎片整理功能

Tips

2. 分配内存

2.1. zmalloc

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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));
    // 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 字节中中存储了 size 的大小
    return (char*)ptr+PREFIX_SIZE;
#endif
}
  • zmalloc_size

    使用 tcmalloc, jemalloc, libmallocptmalloc 时,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
    
  • 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));

2.2. zcalloc

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

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

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

2.3. 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
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 字节中中存储了 size 的大小
    return (char*)newptr+PREFIX_SIZE;
#endif
}
  • 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 两行可以删除。

3. 释放内存

3.1. zfree

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
void zfree(void *ptr) {
#ifndef HAVE_MALLOC_SIZE
    void *realptr;
    size_t oldsize;
#endif
    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
}

4. Utils

4.1. 复制字符串

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;
}

4.2. 获取 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;
}

4.3. 设置异常处理函数

 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;
}