设为首页 加入收藏

TOP

redis源码分析(一)内存管理(二)
2015-07-24 10:44:24 来源: 作者: 【 】 浏览:2
Tags:redis 源码 分析 内存 管理
oc.h有相关宏声明,我们发现,因为 tc_malloc 、je_malloc 和 Mac平台下的 malloc 函数族提供了计算已分配空间大小的函数(分别是tc_malloc_size, je_malloc_usable_size和malloc_size),所以就不需要单独分配一段空间记录大小了。在linux和sun平台则要记录分配空间大小。对于linux,使用sizeof(size_t)定长字段记录;对于sun 系统,使用sizeof(long long)定长字段记录,其对应源码中的 PREFIX_SIZE 宏。

PREFIX_SIZE 有什么用呢?

为了统计当前进程到底占用了多少内存。在 zmalloc.c 中,有一个静态变量:

static size_t used_memory = 0;
这个变量它记录了进程当前占用的内存总数。每当要分配内存或是释放内存的时候,都要更新这个变量(当然可以是线程安全的)。因为分配内存的时候,需要指定分配多少内存。但是释放内存的时候,(对于未提供malloc_size函数的内存库)通过指向要释放内存的指针是不能知道释放的空间到底有多大的。这时候,上面提到的PREFIX_SIZE就起作用了,可以通过其中记录的内容得到空间的大小。(不过在linux系统上也有相应的函数获得分配内存空间的大小,参见这里)。

通过zmalloc的源码我们可以发现,其分配空间代码为void *ptr = malloc(size+PREFIX_SIZE); 显然其分配空间大小为:size+PREFIX_SIZE ,对于使用tc_malloc或je_malloc的情况或mac系统,其 PREFIX_SIZE 为0。当分配失败时有相应的出错处理 。

前面我们已经说过redis通过使用used_memory 的变量来统计当前进程到底占用了多少内存,因此在分配和释放内存时我们需要紧接着更新used_memory 的相应值,对应到redis源码中为:

#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
上面的代码有事宏预处理 #ifdef HAVE_MALLOC_SIZE 显然是上面我们说过的利用的tc_malloc je_malloc Mac等提供malloc_size函数的情形,我们可以很容易得知分配内存的大小通过统一化的malloc_size函数即可。但是对于没有提供malloc_size功能的函数,redis是怎么处理的呢?看上面的源码 #else下面的代码即是其实现,其对应的内存结构如下:
prefix-size memory size
分配的内存前加一个固定大小的prefis-size空间,用于记录该段内存的大小,size所占据的内存大小是已知的,为size_t类型的长度,因此通过*((size_t*)ptr) = size; 即可对当前内存块大小进行指定。每次分配内存后,返回的实际地址指针为指向memorysize的地址( (char*)ptr+PREFIX_SIZE; ),通过该指针,可以很容易的计算出实际内存的头地址,从而释放内存。

redis通过update_zmalloc_stat_alloc(__n,__size) 和 update_zmalloc_stat_free(__n) 这两个宏负责在分配内存或是释放内存的时候更新used_memory变量。update_zmalloc_stat_alloc定义如下:

#define update_zmalloc_stat_alloc(__n) do { \
    size_t _n = (__n); \
    if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \ 
    if (zmalloc_thread_safe) { \
        update_zmalloc_stat_add(_n); \
    } else { \
        used_memory += _n; \
    } \
} while(0)
redis把这个更新操作写成宏的形式主要是处于效率的考虑。

上面的代码中

A,if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1));
主要是考虑对齐问题,保证新增的_n 是 sizeof(long)的倍数。

B, if (zmalloc_thread_safe) { \
update_zmalloc_stat_add(_n); \

}

如果进程中有多个线程存在,并保证线程安全zmalloc_thread_safe,则在更新变量的时候要加锁。 通过宏HAVE_ATOMIC选择相应的同步机制。

zmalloc_calloc、zmalloc_free等的实现就不仔细介绍了详情参见源码。

最后讲解下 zmalloc_get_rss()函数。
这个函数用来获取进程的RSS。神马是RSS?全称为Resident Set Size,指实际使用物理内存(包含共享库占用的内存)。在linux系统中,可以通过读取/proc/pid/stat文件系统获取,pid为当前进程的进程号。读取到的不是byte数,而是内存页数。通过系统调用sysconf(_SC_PAGESIZE)可以获得当前系统的内存页大小。 获得进程的RSS后,可以计算目前数据的内存碎片大小,直接用rss除以used_memory。rss包含进程的所有内存使用,包括代码,共享库,堆栈等。 哪来的内存碎片?上面我们已经说明了通常考虑到效率,往往有内存对齐等方面的考虑,所以,碎片就在这里产生了。相比传统glibc中的malloc的内存利用率不是很高一般会使用别的内存库系统。在redis中默认的已经不使用简单的malloc了而是使用 jemalloc, 在源文件src/Makefile下有这样一段代码:

ifeq ($(uname_S),Linux)	MALLOC=jemalloc
可以知道在linux系统上默认使用jemalloc, 在redis发布的源码中有相关的库 deps/jemalloc 。

总的来说 redis则完全自主分配内存,在请求到的时候实时根据内建的算法分配内存,完全自主控制内存的管理。简单即是没吧,不过功能确实强大。

首页 上一页 1 2 下一页 尾页 2/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇漫谈数据库锁 下一篇朴素贝叶斯算法在垃圾邮件过滤中..

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容:

·C++ Lambda表达式保 (2025-12-26 05:49:45)
·C++ Lambda表达式的 (2025-12-26 05:49:42)
·深入浅出 C++ Lambda (2025-12-26 05:49:40)
·C语言指针从入门到基 (2025-12-26 05:21:36)
·【C语言指针初阶】C (2025-12-26 05:21:33)