redis源码解析之内存管理
zmalloc.h的内容如下:
void *zmalloc(size_t size);
void *zcalloc(size_t size);
void *zrealloc(void *ptr, size_t size);
void zfree(void *ptr);
char *zstrdup(const char *s);
size_t zmalloc_used_memory(void);
void zmalloc_enable_thread_safeness(void);
float zmalloc_get_fragmentation_ratio(void);
size_t zmalloc_get_rss(void);
size_t zmalloc_allocations_for_size(size_t size); #define ZMALLOC_MAX_ALLOC_STAT 256
就这么几行。这是redis的内存管理接口。zmalloc,zcalloc,zrealloc和zfree分别对应c库中的malloc,calloc,realloc和free。zstrdup用于生成一个字符串的拷贝。后面的几个函数用于获取内存使用信息,后面会详细介绍。
在看zmalloc.c的源码之前,先看看redis是怎样管理内存的。redis为了方便内存的管理,在分配一块内存之后,会将这块内存的大小存入内存块的头部。
real_ptr是redis调用malloc后返回的指针。redis将内存块的大小size存入头部,size所占据的内存大小是已知的,为size_t类型的长度,然后返回ret_ptr。当需要释放内存的时候,ret_ptr被传给内存管理程序。通过ret_ptr,程序可以很容易的算出real_ptr的值,然后将real_ptr传给free释放内存。
redis会记录所有的内存分配情况。redis定义一个数组,这个数组的长度为ZMALLOC_MAX_ALLOC_STAT。数组的每一个元素代表当前程序所分配的内存块的个数,且内存块的大小为该元素的下标。在程序中,这个数组为zmalloc_allocations。zmalloc_allocations[16]代表已经分配的长度为16bytes的内存块的个数。zmalloc.c中有一个静态变量used_memory用来记录当前分配的内存总大小。
下面开始分析代码。
首先是定义宏PREFIX_SIZE:
#ifdef HAVE_MALLOC_SIZE
#define PREFIX_SIZE (0)
#else
#if defined(__sun)
#define PREFIX_SIZE (sizeof(long long))
#else
#define PREFIX_SIZE (sizeof(size_t))
#endif
#endif
如果定义了HAVE_MALLOC_SIZE,那么PREFIX_SIZE就为0。这里,HAVE_MALLOC_SIZE用来确定系统是否有函数malloc_size。这个HAVE_MALLOC_SIZE宏在config.h中定义,如下:
/* Use tcmalloc's malloc_size() when available.
2 * When tcmalloc is used, native OSX malloc_size() may never be used because
3 * this expects a different allocation scheme. Therefore, *exclusively* use
4 * either tcmalloc or OSX's malloc_size()! */
#if defined(USE_TCMALLOC)
#include <google/tcmalloc.h>
#if TC_VERSION_MAJOR >= 1 && TC_VERSION_MINOR >= 6
#define HAVE_MALLOC_SIZE 1
#define redis_malloc_size(p) tc_malloc_size(p)
#endif
#elif defined(__APPLE__)
#include <malloc/malloc.h>
#define HAVE_MALLOC_SIZE 1
#define redis_malloc_size(p) malloc_size(p)
#endif
如果使用google的tcmalloc库,那么,redis_malloc_size就对应与tcmalloc库的tc_malloc_size函数。如果是在apple的mac上编译,那么redis_malloc_size就对应与malloc_size。redis_malloc_size的功能是获得参数p所指向的内存块的大小。tcmalloc库在google的google-perftools 库中,据说这个库在内存管理的效率上很惊艳。不过这个库是c++写的,而redis是c写的,两者揉一起还是有点不给力阿。。。
如果没有malloc_size函数,那么在Solaris系统上,用long long类型的长度来定义PREFIX_SIZE,其他系统为size_t的长度。
接着,定义下面这些宏。这些宏的作用是如果使用tcmalloc库,那么将库中的分配函数对应到标准库上。后面的函数可直接使用标准库函数的名称。在更换库的时候不需要更改。
/* Explicitly override malloc/free etc when using tcmalloc. */
#if defined(USE_TCMALLOC)
#define malloc(size) tc_malloc(size)
#define calloc(count,size) tc_calloc(count,size)
#define realloc(ptr,size) tc_realloc(ptr,size)
#define free(ptr) tc_free(ptr)
#endif
下面的两个宏用于更新zmalloc_allocations数组。update_zmalloc_stat_alloc用于在分配内存的时候更新已分配大小,update_zmalloc_stat_free用于在释放内存的时候删除对应的记录。
#define update_zmalloc_stat_alloc(__n,__size) do { \
size_t _n = (__n); \
size_t _stat_slot = (__size < ZMALLOC_MAX_ALLOC_STAT) ? __size : ZMALLOC_MAX_ALLOC_STAT; \
if (_n&(sizeof(long)-)) _n += sizeof(long)-(_n&(sizeof(long)-)); \
if (zmalloc_thread_safe) { \
pthread_mutex_lock(&used_memory_mutex); \
used_memory += _n; \
zmalloc_allocations[_stat_slot]++; \
pthread_mutex_unlock(&used_memory_mutex); \
} else { \
used_memory += _n; \
zmalloc_allocations[_stat_slot]++; \
} \
} while()
update_zmalloc_stat_alloc的第一个参数__n是从系统那实际获得的内存大小,第二个参数是程序请求的内存大小。update_zmalloc_stat_alloc首先判断程序请求的内存大小在zmalloc_allocations数组中对应的下标。如果内存大小大于zmalloc_allocations数组的长度-1,那么其对应的下标是最后一个。然后,将实际分配的内存大小对齐为long类型长度的整数倍(malloc通常会考虑对齐问题,实际分配的内存大小也会因对齐而有所出入,后文会介绍)。最后,在used_memory记录实际分配的大小,在zmalloc_allocations对应位置加一。这里如果设置为线程安全,那么在记录之前要对两个静态变量加锁。
#define update_zmalloc_stat_free(__n) do { \
size_t _n = (__n); \
if (_n&(sizeof(long)-)) _n += sizeof(long)-(_n&(sizeof(long)-)); \
if (zmalloc_thread_safe) { \
pthread_mutex_lock(&used_memory_mutex); \
used_memory -= _n; \
pthread_mutex_unlock(&used_memory_mutex); \
} else { \
used_memory -= _n; \
} \
} while()
update_zmalloc_stat_free和update_zmalloc_stat_alloc差不多,但仅仅减少了used_memory的值。
对于zmalloc,zalloc,zrealloc和zfree这几个函数,仅仅是对标准库的函数的简单的封装。所做的工作除了调用标准库(也可能是tcmalloc库)的函数分配内存外,就是对每次分配和释放内存做合适的记录。如果系统中有malloc_size函数,那么直接调用前面的那两个宏,没什么可讲的。如果没有malloc_size函数,那么需要在所分配的内存头部的PREFIX_SIZE大小的区域内,记录内存块的大小。代码很简单,就一句:
现将ptr转换成size_t类型的指针,然后将size的值赋给其指向的内存。笔者感觉没有必要在前面定义PREFIX_SIZE的时候区分系统,因为这里直接硬编码了内存大小的类型为size_t。前面的宏判断有点多此一举了。这里的size是返回的内存区域的大小,不包括保存大小的头部。
读取内存块大小需要两步:
2 oldsize = *((size_t*)realptr);
程序传进来的是ret_ptr,通过减去PREFIX_SIZE得到real_ptr。从real_ptr所指向的内存中读取大小即可。
其余的几个函数也很直接,没什么好说的。最后讲一讲zmalloc_get_rss()函数。这个函数用来获取进程的RSS。神马是RSS?google reader那个?显然不是。。。全称为Resident Set Size,指实际使用物理内存(包含共享库占用的内存)。在linux系统中,可以通过读取/proc/pid/stat文件获取,pid为当前进程的进程号。读取到的不是byte数,而是内存页数。通过系统调用sysconf(_SC_PAGESIZE)可以获得当前系统的内存页大小。Unix系统貌似可以直接通过task_info直接获取,比linux系统简单的多。
获得进程的RSS后,可以计算目前数据的内存碎片大小,直接用rss除以used_memory。rss包含进程的所有内存使用,包括代码,共享库,堆栈等。但是由于通常情况下redis在内存中数据的量要远远大于这些数据所占用的内存,因此这个简单的计算还是比较准确的。
这里有一个问题,程序都是用多少内存就分配多少内存,哪来的内存碎片?其实,当调用malloc的时候,malloc并不是严格按照参数的值来分配内存。比如,程序只请求一个byte的内存,malloc不会就只分配一个byte,通常,基于内存对齐等方面的考虑,malloc会分配4个byte。这样,如果程序中大量请求1byte内存,那么实际使用的是所请求的4倍。malloc进行小内存分配是很浪费的。所以,碎片就在这里产生了。
总的来说,redis的内存管理简单粗暴,没有神马复杂的引用计数等技术。但是,很多时候,简单的往往是高效且合理的。redis内存的中数据通常会是几个G,这个方法快速,统计结果也很精确。
#define update_zmalloc_stat_alloc(__n) do { \
size_t _n = (__n); \
if (_n&(sizeof(long)-)) _n += sizeof(long)-(_n&(sizeof(long)-)); \
if (zmalloc_thread_safe) { \
update_zmalloc_stat_add(_n); \
} else { \
used_memory += _n; \
} \
} while()
redis源码解析之内存管理的更多相关文章
- Samba 源码解析之内存管理
由于工作需要想研究下Samba的源码,下载后发现目录结构还是很清晰的.一般大家可能会对source3和source4文件夹比较疑惑.这两个文件夹针对的是Samba主版本号,所以你可以暂时先看一个.这里 ...
- Memcached源码分析之内存管理
先再说明一下,我本次分析的memcached版本是1.4.20,有些旧的版本关于内存管理的机制和数据结构与1.4.20有一定的差异(本文中会提到). 一)模型分析在开始解剖memcached关于内存管 ...
- .Net Core缓存组件(Redis)源码解析
上一篇文章已经介绍了MemoryCache,MemoryCache存储的数据类型是Object,也说了Redis支持五中数据类型的存储,但是微软的Redis缓存组件只实现了Hash类型的存储.在分析源 ...
- Redis源码解析:15Resis主从复制之从节点流程
Redis的主从复制功能,可以实现Redis实例的高可用,避免单个Redis 服务器的单点故障,并且可以实现负载均衡. 一:主从复制过程 Redis的复制功能分为同步(sync)和命令传播(comma ...
- Redis源码解析之跳跃表(三)
我们再来学习如何从跳跃表中查询数据,跳跃表本质上是一个链表,但它允许我们像数组一样定位某个索引区间内的节点,并且与数组不同的是,跳跃表允许我们将头节点L0层的前驱节点(即跳跃表分值最小的节点)zsl- ...
- Redis源码解析:13Redis中的事件驱动机制
Redis中,处理网络IO时,采用的是事件驱动机制.但它没有使用libevent或者libev这样的库,而是自己实现了一个非常简单明了的事件驱动库ae_event,主要代码仅仅400行左右. 没有选择 ...
- Redis源码解析:25集群(一)握手、心跳消息以及下线检测
Redis集群是Redis提供的分布式数据库方案,通过分片来进行数据共享,并提供复制和故障转移功能. 一:初始化 1:数据结构 在源码中,通过server.cluster记录整个集群当前的状态,比如集 ...
- Python 源码学习之内存管理 -- (转)
Python 的内存管理架构(Objects/obmalloc.c): _____ ______ ______ ________ [ int ] [ dict ] [ list ] ... [ str ...
- Redis源码解析之ziplist
Ziplist是用字符串来实现的双向链表,对于容量较小的键值对,为其创建一个结构复杂的哈希表太浪费内存,所以redis 创建了ziplist来存放这些键值对,这可以减少存放节点指针的空间,因此它被用来 ...
随机推荐
- Ribbon的主要组件与工作流程
一:Ribbon是什么? Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起.Ribbon客户端组件提供一系列完善的配置项如连接 ...
- Python3 shelve模块(持久化)
shelve模块 也可以序列化Python所有数据类型,而且可以多次序列化;shelve模块通过key-value方式持久化 1.序列化 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 ...
- 安全测试===sqlmap(贰)转载
十二.列举数据 这些参数用于列举出数据库管理系统信息.数据结构和数据内容. 1.一键列举全部数据 参数:--all 使用这一个参数就能列举所有可访问的数据.但不推荐使用,因为这会发送大量请求,把有用和 ...
- HDU 6186 CS Course 前缀和,后缀和
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6186 题意:给了n个数,然后有q个查询,每个查询要求我们删掉一个数,问删掉这个数后整个序列的与值,或值 ...
- Atom:优雅迷人的编辑神器
对于热爱markdown写作的人来说,Atom同样是一款拥有无穷魅力的写作软件.我不怕它无法满足你的需求,就怕你不给一个机会了解它,那么,这将是一场遗憾的错过. 大学的时候,坊间对那些编程高手有一个令 ...
- springboot项目的搭建
原文链接:http://www.cnblogs.com/winner-0715/p/6666302.html 后续完善(附图及详细过程)
- windows server 2012 IIS配置之FTP站点
原文地址:[原创]winserver2012IIS配置之FTP站点作者:hkmysterious 一.实验拓扑: 使server2012客户计算机通过ftp方式从FTP服务器上下载已上传并共享的文 ...
- vscode的go插件安装
vscode安装go的很多插件都是失败,如下: Installing 5 tools at E:\www\go_project\bin go-symbols guru gorename goretur ...
- vue2.0--组件通信(非vuex法)
写在前面: 1.父组件的data写法与子组件的data写法不同 //父组件 data:{ //对象形式 } //子组件 data:function(){ return { //函数形式 } } 2.引 ...
- python链接mysql以及mysql中对表修改的常用语法
MySQL是一个关系型数据库管理系统 ,其体积小.速度快.总体拥有成本低,尤其是开放源码这一特点,一般中小型网站的开发都选择 MySQL 作为网站数据库.在使用过程中不总是和它打交道,导致使用时候都得 ...