redis的内存分配主要就是对malloc和free进行了一层简单的封装。具体的实现在zmalloc.h和zmalloc.c中。本文将对redis的内存管理相关几个比较重要的函数做逐一的介绍

参考:

  1. http://blog.csdn.net/guodongxiaren/article/details/44783767
  2. http://www.voidcn.com/article/p-kxxvjygo-bpm.html
  3. http://blog.ddup.us/2011/05/11/redis-internal-memory-allocation/
  4. http://blog.csdn.net/taozhi20084525/article/details/23621345
void *zmalloc(size_t size);
void *zcalloc(size_t size);
void *zrealloc(void *ptr, size_t size);
void zfree(void *ptr);
size_t zmalloc_used_memory(void);
void zmalloc_enable_thread_safeness(void);
float zmalloc_get_fragmentation_ratio(size_t rss);
size_t zmalloc_get_rss(void);

zmalloc

在zmalloc函数中,实际可能会每次多申请一个 PREFIX_SIZE的空间。从如下的代码中看出,如果定义了宏HAVE_MALLOC_SIZE,那么 PREFIX_SIZE的长度为0。其他的情况下,都会多分配至少8字节的长度的内存空间。

  • zmalloc.c
#ifdef HAVE_MALLOC_SIZE
#define PREFIX_SIZE (0)
#else
#if defined(__sun) || defined(__sparc) || defined(__sparc__)
#define PREFIX_SIZE (sizeof(long long))
#else
#define PREFIX_SIZE (sizeof(size_t))
#endif
#endif
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));
return ptr;
#else
*((size_t*)ptr) = size;
update_zmalloc_stat_alloc(size+PREFIX_SIZE);
return (char*)ptr+PREFIX_SIZE;
#endif
}

这么做的原因是因为: tcmalloc 和 Mac平台下的 malloc 函数族提供了计算已分配空间大小的函数(分别是tcmallocsize和mallocsize),所以就不需要单独分配一段空间记录大小了。而针对linux和sun平台则要记录分配空间大小。对于linux,使用sizeof(sizet)定长字段记录;对于sun os,使用sizeof(long long)定长字段记录。因此当宏HAVE_MALLOC_SIZE没有被定义的时候,就需要在多分配出的空间内记录下当前申请的内存空间的大小。

update_zmalloc_stat_alloc

update_zmalloc_stat_alloc 是一个宏,因为sizeof(long) == 8 [64位系统中],所以其实第一个if的代码等价于if(_n&7) _n += 8 - (_n&7); 这段代码就是判断分配的内存空间的大小是不是8的倍数。如果内存大小不是8的倍数,就加上相应的偏移量使之变成8的倍数。_n&7 在功能上等价于 _n%8,不过位操作的效率显然更高。

    #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)

malloc函数本身能够保证分配的内存是8字节对齐的,如果要分配的内存不是8的倍数,那么malloc就会多分配一点,来凑成8的倍数。所以这段代码真正的作用是获得使用内存的精确大小。

第二个if主要判断当前是否处于线程安全的情况下。如果处于线程安全的情况下,就使用update_zmalloc_stat_add宏来更改全局变量used_memory。否则的话就直接加上n。

    #define update_zmalloc_stat_add(__n) do { \
pthread_mutex_lock(&used_memory_mutex); \
used_memory += (__n); \
pthread_mutex_unlock(&used_memory_mutex); \
} while(0)

zmalloc_size

在zmalloc.h的代码中,有一段如下定义的代码:

    #if defined(USE_TCMALLOC)
#define ZMALLOC_LIB ("tcmalloc-" __xstr(TC_VERSION_MAJOR) "." __xstr(TC_VERSION_MINOR))
#include <google/tcmalloc.h>
#if (TC_VERSION_MAJOR == 1 && TC_VERSION_MINOR >= 6) || (TC_VERSION_MAJOR > 1)
#define HAVE_MALLOC_SIZE 1
#define zmalloc_size(p) tc_malloc_size(p)
#else
#error "Newer version of tcmalloc required"
#endif #elif defined(USE_JEMALLOC)
#define ZMALLOC_LIB ("jemalloc-" __xstr(JEMALLOC_VERSION_MAJOR) "." __xstr(JEMALLOC_VERSION_MINOR) "." __xstr(JEMALLOC_VERSION_BUGFIX))
#include <jemalloc/jemalloc.h>
#if (JEMALLOC_VERSION_MAJOR == 2 && JEMALLOC_VERSION_MINOR >= 1) || (JEMALLOC_VERSION_MAJOR > 2)
#define HAVE_MALLOC_SIZE 1
#define zmalloc_size(p) je_malloc_usable_size(p)
#else
#error "Newer version of jemalloc required"
#endif #elif defined(__APPLE__)
#include <malloc/malloc.h>
#define HAVE_MALLOC_SIZE 1
#define zmalloc_size(p) malloc_size(p)
#endif #ifndef ZMALLOC_LIB
#define ZMALLOC_LIB "libc"
#endif

可以看如果使用了jemalloc tcmalloc 或者apple系统下,都提供了检测内存块大小的函数,因此 zmalloc_size就使用相应的库函数。如果默认使用libc的话则 zmalloc_size函数有以下的定义:

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

zfree

有分配就有内存回收,zfree 函数就是实现内存回收的功能。

   void zfree(void *ptr) {
#ifndef HAVE_MALLOC_SIZE
void *realptr;
size_t oldsize;
#endif if (ptr == NULL) return;
#ifdef HAVE_MALLOC_SIZE
update_zmalloc_stat_free(zmalloc_size(ptr));
free(ptr);
#else
realptr = (char*)ptr-PREFIX_SIZE;
oldsize = *((size_t*)realptr);
update_zmalloc_stat_free(oldsize+PREFIX_SIZE);
free(realptr);
#endif
}

上面的代码可以看出,根据用的库不相同,回收的时候也采用了不同的方法。

   #else
realptr = (char*)ptr-PREFIX_SIZE;
oldsize = *((size_t*)realptr);
update_zmalloc_stat_free(oldsize+PREFIX_SIZE);
free(realptr);
#endif

可以发现如果使用的libc库,则需要将ptr指针向前偏移8个字节的长度,回退到最初malloc返回的地址,然后通过类型转换再取指针所指向的值。通过zmalloc()函数的分析,可知这里存储着我们最初需要分配的内存大小(zmalloc中的size),这里赋值给oldsize。update_zmalloc_stat_free()也是一个宏函数,和zmalloc中update_zmalloc_stat_alloc()大致相同,唯一不同之处是前者在给变量used_memory减去分配的空间,而后者是加上该空间大小。

最后free(realptr),清除空间。

zcalloc

zcalloc 函数与zmalloc函数的功能基本相同,但有2点不同的是:

  1. 分配的空间大小是 size * nmemb。;
  2. calloc()会对分配的空间做初始化工作(初始化为0),而malloc()不会。
void *zcalloc(size_t size) {
void *ptr = calloc(1, size+PREFIX_SIZE); if (!ptr) zmalloc_oom_handler(size);
#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
}

zrealloc

zrealloc 函数用来修改内存大小。具体的流程基本是分配新的内存大小,然后把老的内存数据拷贝过去,之后释放原有的内存。

    void *zrealloc(void *ptr, size_t size) {
#ifndef HAVE_MALLOC_SIZE
void *realptr;
#endif
size_t oldsize;
void *newptr; if (ptr == NULL) return zmalloc(size);
#ifdef HAVE_MALLOC_SIZE
oldsize = zmalloc_size(ptr);
newptr = realloc(ptr,size);
if (!newptr) zmalloc_oom_handler(size); update_zmalloc_stat_free(oldsize);
update_zmalloc_stat_alloc(zmalloc_size(newptr));
return newptr;
#else
realptr = (char*)ptr-PREFIX_SIZE;
oldsize = *((size_t*)realptr);
newptr = realloc(realptr,size+PREFIX_SIZE);
if (!newptr) zmalloc_oom_handler(size); *((size_t*)newptr) = size;
update_zmalloc_stat_free(oldsize);
update_zmalloc_stat_alloc(size);
return (char*)newptr+PREFIX_SIZE;
#endif
}

zmalloc_used_memory

zmalloc_used_memory 函数用来获取当前使用的内存总量,其中__sync_add_and_fetch就是宏update_zmalloc_stat_add。关于do while(0)的用法可以参见http://blog.csdn.net/luoweifu/article/details/38563161

size_t zmalloc_used_memory(void) {
size_t um; if (zmalloc_thread_safe) {
#ifdef HAVE_ATOMIC
um = __sync_add_and_fetch(&used_memory, 0);
#else
pthread_mutex_lock(&used_memory_mutex);
um = used_memory;
pthread_mutex_unlock(&used_memory_mutex);
#endif
}
else {
um = used_memory;
} return um;
}

zmalloc_get_rss

这个函数可以获取当前进程实际所驻留在内存中的空间大小,即不包括被交换(swap)出去的空间。该函数大致的操作就是在当前进程的 /proc//stat 【表示当前进程id】文件中进行检索。该文件的第24个字段是RSS的信息,它的单位是pages(内存页的数目)。如果没从操作系统的层面获取驻留内存大小,那就只能绌劣的返回已经分配出去的内存大小。

/* Get the RSS information in an OS-specific way.
*
* WARNING: the function zmalloc_get_rss() is not designed to be fast
* and may not be called in the busy loops where Redis tries to release
* memory expiring or swapping out objects.
*
* For this kind of "fast RSS reporting" usages use instead the
* function RedisEstimateRSS() that is a much faster (and less precise)
* version of the function. */ #if defined(HAVE_PROC_STAT)
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> size_t zmalloc_get_rss(void) {
int page = sysconf(_SC_PAGESIZE);
size_t rss;
char buf[4096];
char filename[256];
int fd, count;
char *p, *x; snprintf(filename,256,"/proc/%d/stat",getpid());
if ((fd = open(filename,O_RDONLY)) == -1) return 0;
if (read(fd,buf,4096) <= 0) {
close(fd);
return 0;
}
close(fd); p = buf;
count = 23; /* RSS is the 24th field in /proc/<pid>/stat */
while(p && count--) {
p = strchr(p,' ');
if (p) p++;
}
if (!p) return 0;
x = strchr(p,' ');
if (!x) return 0;
*x = '\0'; rss = strtoll(p,NULL,10);
rss *= page;
return rss;
}
#elif defined(HAVE_TASKINFO)
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <mach/task.h>
#include <mach/mach_init.h> size_t zmalloc_get_rss(void) {
task_t task = MACH_PORT_NULL;
struct task_basic_info t_info;
mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT; if (task_for_pid(current_task(), getpid(), &task) != KERN_SUCCESS)
return 0;
task_info(task, TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count); return t_info.resident_size;
}
#else
size_t zmalloc_get_rss(void) {
/* If we can't get the RSS in an OS-specific way for this system just
* return the memory usage we estimated in zmalloc()..
*
* Fragmentation will appear to be always 1 (no fragmentation)
* of course... */
return zmalloc_used_memory();
}
#endif

zmalloc_get_fragmentation_ratio

这个函数可以来提供内存碎片率的指标,直接用驻留在物理内存中的内存/除以分配的总物理内存,得到一个所谓的碎片率, 实际留在物理内存中的除以总分配的。

/* Fragmentation = RSS / allocated-bytes */
float zmalloc_get_fragmentation_ratio(size_t rss) {
return (float)rss/zmalloc_used_memory();
}

redis源码笔记-内存管理zmalloc.c的更多相关文章

  1. redis源码解读--内存分配zmalloc

    目录 主要函数 void *zmalloc(size_t size) void *zcalloc(size_t size) void zrealloc(void ptr, size_t size) v ...

  2. redis源码笔记(一) —— 从redis的启动到command的分发

    本作品采用知识共享署名 4.0 国际许可协议进行许可.转载联系作者并保留声明头部与原文链接https://luzeshu.com/blog/redis1 本博客同步在http://www.cnblog ...

  3. Redis源码分析:serverCron - redis源码笔记

    [redis源码分析]http://blog.csdn.net/column/details/redis-source.html   Redis源代码重要目录 dict.c:也是很重要的两个文件,主要 ...

  4. Redis源码笔记--服务器日志和函数可变参数处理server.c

    前言 Redis源码中定义了几个和日志相关的函数,用于将不同级别的信息打印到不同的位置(日志文件或标准输出,取决于配置文件的设置),这些函数的定义位于 server.h 和server.c 文件中,包 ...

  5. Redis源码笔记-初步

    目录 目录 1 1. 前言 2 2. 名词 2 3. dict.c 2 3.1. siphash算法 2 3.2. 核心函数 3 3.3. 核心宏 3 3.4. 核心结构体 3 3.4.1. dict ...

  6. MongoDB源码概述——内存管理和存储引擎

    原文地址:http://creator.cnblogs.com/ 数据存储: 之前在介绍Journal的时候有说到为什么MongoDB会先把数据放入内存,而不是直接持久化到数据库存储文件,这与Mong ...

  7. redis源代码解读之内存管理————zmalloc文件

    本文章主要记录本人在看redis源代码的一些理解和想法.由于功力有限,肯定会出现故障,所以.希望高手给出指正. 第一篇就是内存相关的介绍.由于我喜欢先看一些组件的东西,再看总体的流程. 先上一下代码吧 ...

  8. Memcached源码分析——内存管理

    注:这篇内容极其混乱 推荐学习这篇博客.博客的地址:http://kenby.iteye.com/blog/1423989 基本元素item item是Memcached中记录存储的基本单元,用户向m ...

  9. Paddle源码之内存管理技术

    前言 在深度学习模型训练中,每次迭代过程中都涉及到Tensor的创建和销毁,伴随着的是内存的频繁 malloc和free操作,可能对模型训练带来不必要的 overhead. 在主流的深度学习框架中,会 ...

随机推荐

  1. WCF SOAP用法

    基本思路 1.新建一个WCF服务库2.在客户端引用处右键,添加服务引用   点击发现,选择目标服务设置好命名空间   可以在高级一栏里面,设置详细信息   点击确认,添加服务引用 3.在客户端自动生成 ...

  2. 从Windows系统服务获取活动用户的注册表信息(当前活动用户的sessionId. 当前活动用户的 hUserToken)

    首先,对“活动用户”的定义是,当前拥有桌面的用户.对于Windows XP及其以后的系统,即使是可以多个用户同时登录了,拥有桌面的也仅仅只有一个. 如果系统级服务调用Windows API来获取注册表 ...

  3. 创建 DLL 步骤 和 SRC

    LIBRARY SimulationTouchDll EXPORTS MouseControl GetPosition //MouseControlInterface.def 文件 #pragma o ...

  4. EF ModelFirst 步骤

    1 新建实体数据模型 ,选择空模型 2 建各个实体,并指定各字段属性,字符串需要指定长度 3 添加关系,选择关系类型,勾选添加外键 4 新建空的数据库,然后在edmx文件的空白处点右键,选择从模型生成 ...

  5. AY的Dapper研究学习-基本入门-C#开发-aaronyang技术分享

    原文:AY的Dapper研究学习-基本入门-C#开发-aaronyang技术分享 ====================www.ayjs.net       杨洋    wpfui.com      ...

  6. FileHelper

    using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Win ...

  7. Android项目实战(四十四):Zxing二维码切换横屏扫描

    原文:Android项目实战(四十四):Zxing二维码切换横屏扫描 Demo链接 默认是竖屏扫描,但是当我们在清单文件中配置横屏显示的时候: <activity android:name=&q ...

  8. qml实现对SSL的支持(使用msys2,同时支持32和64位)超详细 good

    首先准备环境.两种方法,使用mingw64 或者VS 直接放上下载地址https://sourceforge.net/projects/msys2/我下载的是msys2-x86_64-20161025 ...

  9. 如何计算memcache的容量

    在容量足够的情况下,当然是越大越好,但这样会造成浪费.不考虑这种情况.我们一般的情况是: memcache集群一开始创建会根据存储的数据量与访问量进行容量大小的估算.再算一个20%的冗余. 在网站快速 ...

  10. .NET Core2.1项目在Linux上使用验证码报Unable to load shared library 'gdiplus' or one of its dependencies

    -- ::, 线程ID:[] 日志级别:ERROR 出错类:WebApp.HttpGlobalExceptionFilter property:[(null)] - 错误描述:System.TypeI ...