您的位置:首页 > 数据库 > Redis

Redis源码分析-TCMalloc

2013-09-26 18:16 260 查看
redis很多地方都在调用zmalloc函数

zmalloc在这里定义zmalloc.c

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
}


malloc的定义

/* 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)
#elif defined(USE_JEMALLOC)
#define malloc(size) je_malloc(size)
#define calloc(count,size) je_calloc(count,size)
#define realloc(ptr,size) je_realloc(ptr,size)
#define free(ptr) je_free(ptr)
#endif


编译时的src目录下的makefile文件中的选项:

ifeq ($(MALLOC),tcmalloc)
FINAL_CFLAGS+= -DUSE_TCMALLOC
FINAL_LIBS+= -ltcmalloc
endif

ifeq ($(MALLOC),tcmalloc_minimal)
FINAL_CFLAGS+= -DUSE_TCMALLOC
FINAL_LIBS+= -ltcmalloc_minimal
endif

ifeq ($(MALLOC),jemalloc)
DEPENDENCY_TARGETS+= jemalloc
FINAL_CFLAGS+= -DUSE_JEMALLOC -I../deps/jemalloc/include
FINAL_LIBS+= ../deps/jemalloc/lib/libjemalloc.a -ldl
endif


将tcmalloc通过“-ltcmalloc”链接器标志接入模块

Tcmalloc概述

tcmalloc将内存请求分为两类,大对象请求和小对象请求,大对象为>=32K的对象。|

tcmalloc会为每个线程分配线程局部缓冲

对于小对象请求,可以直接从线程局部缓冲区获取,如果线程局部缓冲区没有空闲内存,则从central heap中一次性获取一连串小对象。

tcmalloc对于小内存,按8的整数次倍分配,对于大内存,按4K的整数次倍分配。

这样做有两个好处,一是分配的时候比较快。二是短期的收益比较大,分配的小内存至多浪费7个字节,大内存则4K

当某个线程缓存当缓存中所有对象的总共大小超过2MB的时候,会对他进行垃圾收集。垃圾收集阈值会自动根据线程数量的增加而减少,这样就不会因为程序有大量线程而过度浪费内存。

内存泄露检测

使用Tcmalloc的程序,用valgrind无法检测内存泄露,可以使用google-perftools提供的heap checker

使用方法:

export HEAPCHECK=TYPE

TYPE可以为:minimal、normal、strict、draconian

关于Tcmalloc

http://code.google.com/p/gperftools/downloads/list

Tcmalloc通过preload或者直接动态链接的方式对malloc等内存分配和释放函数进行截获并提供服务。Tcmalloc提供接口主要涵盖malloc.h的接口

使用
要使用TCMalloc,只要将tcmalloc通过“-ltcmalloc”链接器标志接入你的应用即可。
你也可以通过使用LD_PRELOAD在不是你自己编译的应用中使用tcmalloc:
$ LD_PRELOAD=”/usr/lib/libtcmalloc.so”
LD_PRELOAD比较讨巧,我们也不十分推荐这种用法。
TCMalloc还包含了一个堆检查器以及一个堆测量器
如果你更想链接不包含堆测量器和检查器的TCMalloc版本(比如可能为了减少静态二进制文件的大小),你可以接入libtcmalloc_minimal。
TCMalloc给每个线程分配了一个线程局部缓存。小分配可以直接由线程局部缓存来满足。需要的话,会将对象从中央数据结构移动到线程局部缓存中,同时定期的垃圾收集将用于把内存从线程局部缓存迁移回中央数据结构中。

TCMalloc将尺寸小于<=

32K的对象(“小”对象)和大对象区分开来。大对象直接使用页级分配器(一个页是一个4K的对齐内存区域)从中央堆直接分配。即,一个大对象总是页对齐的并占据了整数个数的页。
连续的一些页面可以被分割为一系列小对象,而他们的大小都相同。例如,一个连续的页面(4K)可以被划分为32个128字节的对象。
小对象的分配
每个小对象的大小都会被映射到170个可分配的尺寸类别中的一个。例如,在分配961到1024字节时,都会归整为1024字节。尺寸类别这样隔开:较小的尺寸相差8字节,较大的尺寸相差16字节,再大一点的尺寸差32字节,如此类推。最大的间隔(对于尺寸 >= ~2K的)是256字节。

大对象的分配
一个大对象的尺寸(> 32K)会被除以一个页面尺寸(4K)并取整(大于结果的最小整数),同时是由中央页面堆来处理的。中央页面堆又是一个自由列表的阵列。对于i < 256而言,第k个条目是一个由k个页面组成的自由列表。

跨度(Span)
TCMalloc管理的堆由一系列页面组成。连续的页面由一个“跨度”(Span)对象来表示。一个跨度可以是已被分配或者是自由的。如果是自由的,跨度则会是一个页面堆链表中的一个条目。如果已被分配,它会是一个已经被传递给应用程序的大对象,或者是一个已经被分割成一系列小对象的一个页面。如果是被分割成小对象的,对象的尺寸类别会被记录在跨度中。

解除分配
当一个对象被解除分配时,我们先计算他的页面号并在中央阵列中查找对应的跨度对象。该跨度会告诉我们该对象是大是小,如果它是小对象的话尺寸类别是什么。如果是小对象的话,我们将其插入到当前线程的线程缓存中对应的自由列表中。如果线程缓存现在超过了某个预定的大小(默认为2MB),我们便运行垃圾收集器将未使用的对象从线程缓存中移入中央自由列表。
如果该对象是大对象的话,跨度会告诉我们该对象覆盖的页面的范围。假设该范围是[p,q]。我们还会查找页面p-1和页面q+1对应的跨度。如果这两个相邻的跨度中有任何一个是自由的,我们将他们和[p,q]的跨度接合起来。最后跨度会被插入到页面堆中合适的自由列表中。

小对象的中央自由列表
就像前面提过的一样,我们为每一个尺寸类别设置了一个中央自由列表。每个中央自由列表由两层数据结构来组成:一系列跨度和每个跨度一个自由对象的链表。

线程缓存的垃圾收集
某个线程缓存当缓存中所有对象的总共大小超过2MB的时候,会对他进行垃圾收集。垃圾收集阈值会自动根据线程数量的增加而减少,这样就不会因为程序有大量线程而过度浪费内存。
我们会遍历缓存中所有的自由列表并且将一定数量的对象从自由列表移到对于得中央列表中。

注意

TCMalloc可能要比其他malloc版本在某种程度上更吃内存,(但是倾向于不会有其他malloc版本中可能出现的爆发性增长。)尤其是在启动时TCMalloc会分配大约240KB的内部内存。
不要试图将TCMalloc载入到一个运行中的二进制程序中(例如,在Java中使用JNI)。二进制程序已经使用系统malloc分配了一些对象,并会尝试将它们传递到TCMalloc进行解除分配。TCMalloc是无法处理这种对象的。
翻译参考:http://blog.163.com/cp7618@yeah/blog/static/70234777201251345350339/
原文地址:http://google-perftools.googlecode.com/svn/trunk/doc/tcmalloc.html

更多参考:glibc内存泄露以及TCmalloc 简单分析

使用Google的开源TCMalloc库,提高MySQL在高并发情况下的性能[张宴原创]

TCMalloc:线程缓存的Malloc

深入Linux的内存管理,关于PTMalloc3、Hoard和TCMalloc

在应用程序中替换Linux中Glibc的malloc的四种方法

Google performance Tools (gperftools) 使用心得
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: