免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 4186 | 回复: 1

[Redis] Redis源码分析-内存分配 [复制链接]

论坛徽章:
0
发表于 2012-02-16 18:15 |显示全部楼层
Redis源码分析-内存分配






本文转载自Day Day Up博客,文章对Redis的内存分配封装zmalloc库进行了分析,描述了Redis在内存分配和使用统计方面的各种细节和技巧。

原文链接:blog.ddup.us

Redis中到处都会进行内存分配操作。为了屏蔽不同平台之间的差异,以及统计内存占用量等,Redis对内存分配函数进行了一层封装,程序中统一使用zmalloc,zfree一系列函数,位于zmalloc.h,zmalloc.c文中。

上边说过,封装就是为了屏蔽底层平台的差异,同时方便自己实现相关的统计函数。具体来说就是:

•若系统中存在Google的TC_MALLOC库,则使用tc_malloc一族函数代替原本的malloc一族函数。
•若当前系统是Mac系统,则使用<malloc/malloc.h>中的内存分配函数。
•其他情况,在每一段分配好的空间前头,同时多分配一个定长的字段,用来记录分配的空间大小。
源代码分别在 config.h 和 zmalloc.c 中:
  1. /* config.h */
  2. #if defined(USE_TCMALLOC)
  3. #include <google/tcmalloc.h>
  4. #if TC_VERSION_MAJOR >= 1 && TC_VERSION_MINOR >= 6
  5. #define HAVE_MALLOC_SIZE 1
  6. #define redis_malloc_size(p) tc_malloc_size(p)
  7. #endif
  8. #elif defined(__APPLE__)
  9. #include <malloc/malloc.h>
  10. #define HAVE_MALLOC_SIZE 1
  11. #define redis_malloc_size(p) malloc_size(p)
  12. #endif/* zmalloc.c */
  13. #ifdef HAVE_MALLOC_SIZE
  14. #define PREFIX_SIZE (0)
  15. #else
  16. #if defined(__sun)
  17. #define PREFIX_SIZE (sizeof(long long))
  18. #else
  19. #define PREFIX_SIZE (sizeof(size_t))
  20. #endif
  21. #endif
复制代码
因为 tc_malloc 和 Mac平台下的 malloc 函数族提供了计算已分配空间大小的函数(分别是tc_malloc_size和malloc_size),所以就不需要单独分配一段空间记录大小了。而针对linux和sun平台则要记录分配空间大小。对于linux,使用sizeof(size_t)定长字段记录;对于sun os,使用sizeof(long long)定长字段记录。也就是上边源码中的 PREFIX_SIZE 宏。

那么这个记录有什么用呢?答案是,为了统计当前进程到底占用了多少内存。在 zmalloc.c 中,有这样一个静态变量:

static size_t used_memory = 0;它记录了进程当前占用的内存总数。每当要分配内存或是释放内存的时候,都要更新这个变量。因为分配内存的时候,可以明确知道要分配多少内存。但是释放内存的时候,(对于未提供malloc_size函数的平台)仅通过指向要释放内存的指针是不能知道释放的空间到底有多大的。这个时候,上边提到的PREFIX_SIZE定长字段就起作用了,可以通过其中记录的内容得到空间的大小。zmalloc函数如下(去掉无关代码):

  1. void *zmalloc(size_t size) {
  2.     void *ptr = malloc(size+PREFIX_SIZE);

  3.     if (!ptr) zmalloc_oom(size);
  4.     *((size_t*)ptr) = size;
  5.     update_zmalloc_stat_alloc(size+PREFIX_SIZE,size);
  6.     return (char*)ptr+PREFIX_SIZE;
  7. #endif
  8. }
复制代码
看到在分配空间的时候,空间大小是size+PREFIX_SIZE。对于mac系统或是使用tc_malloc的情况,PREFIX_SIZE 为0。之后将ptr指针指向的空间前size_t中记录分配空间的大小。最后返回的是越过记录区的指针。zfree函数类似(去掉无关代码):
  1. void zfree(void *ptr) {
  2.     void *realptr;
  3.     size_t oldsize;

  4.     if (ptr == NULL) return;
  5.     realptr = (char*)ptr-PREFIX_SIZE;
  6.     oldsize = *((size_t*)realptr);
  7.     update_zmalloc_stat_free(oldsize+PREFIX_SIZE);
  8.     free(realptr);
  9. #endif
  10. }
复制代码
先将指针向前移动PREFIX_SIZE,然后取出分配空间时保存的空间长度。最后free整个空间。

update_zmalloc_stat_alloc(__n,__size) 和 update_zmalloc_stat_free(__n) 这两个宏负责在分配内存或是释放内存的时候更新used_memory变量。定义成宏主要是出于效率上的考虑。将其还原为函数,就是下边这个样子:
  1. void update_zmalloc_stat_alloc(__n,__size)
  2. {
  3.     do {
  4.         size_t _n = (__n);
  5.         size_t _stat_slot = (__size < ZMALLOC_MAX_ALLOC_STAT) ? __size : ZMALLOC_MAX_ALLOC_STAT;
  6.         if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1));
  7.         if (zmalloc_thread_safe) {
  8.             pthread_mutex_lock(&used_memory_mutex);
  9.             used_memory += _n;
  10.             zmalloc_allocations[_stat_slot]++;
  11.             pthread_mutex_unlock(&used_memory_mutex);
  12.         } else {
  13.             used_memory += _n;
  14.             zmalloc_allocations[_stat_slot]++;
  15.         }
  16.     } while(0)
  17. }

  18. void update_zmalloc_stat_free(__n)
  19. {
  20.     do {
  21.         size_t _n = (__n);
  22.         if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1));
  23.         if (zmalloc_thread_safe) {
  24.             pthread_mutex_lock(&used_memory_mutex);
  25.             used_memory -= _n;
  26.             pthread_mutex_unlock(&used_memory_mutex);
  27.         } else {
  28.             used_memory -= _n;
  29.         }
  30.     } while(0)
  31. }
复制代码
代码中除了更新used_memory变量外,还有几个要关注的地方:

1.先对_n的低位向上取整,最后_n变为sizeof(long)的倍数,比如对于32位系统,sizeof(long) == 100(二进制),_n向上取整之后,低两位都变为0。
2.如果进程中有多个线程存在,则在更新变量的时候要加锁。
3.在zmalloc函数中还有一个统计量要更新:zmalloc_allocations[]。
在 zmalloc.c 中,zmalloc_allocations是这样定义的:

size_t zmalloc_allocations[ZMALLOC_MAX_ALLOC_STAT+1];其作用是统计程序分配内存时,对不同大小空间的请求次数。统计的空间范围从1字节到256字节,大于256字节的算为256。统计结果通过调用 zmalloc_allocations_for_size 函数返回:
  1. size_t zmalloc_allocations_for_size(size_t size) {
  2.     if (size > ZMALLOC_MAX_ALLOC_STAT) return 0;
  3.     return zmalloc_allocations[size];
  4. }
复制代码
另一个对内存使用量的统计通过调用 zmalloc_used_memory 函数返回:
  1. size_t zmalloc_used_memory(void) {
  2.     size_t um;

  3.     if (zmalloc_thread_safe) pthread_mutex_lock(&used_memory_mutex);
  4.     um = used_memory;
  5.     if (zmalloc_thread_safe) pthread_mutex_unlock(&used_memory_mutex);
  6.     return um;
  7. }
复制代码
另外 zmalloc.c 中,还针对不同的系统实现了 zmalloc_get_rss 函数,在linux系统中是通过读取/proc/$pid/stat文件获得系统统计的内存占用量。关于/proc虚拟文件系统可以看之前的文章。

论坛徽章:
0
发表于 2012-02-17 22:58 |显示全部楼层
谢谢分享
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

DTCC2020中国数据库技术大会 限时8.5折

【架构革新 高效可控】2020年6月4日~6日第十一届中国数据库技术大会将在北京隆重召开。

大会设置2大主会场,20+技术专场,将邀请超百位行业专家,重点围绕数据架构、AI与大数据、传统企业数据库实践和国产开源数据库等内容展开分享和探讨,为广大数据领域从业人士提供一场年度盛会和交流平台。

http://dtcc.it168.com


大会官网>>
  

北京盛拓优讯信息技术有限公司. 版权所有 16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122
中国互联网协会会员  联系我们:huangweiwei@it168.com
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP