2012年tcmalloc学习笔记之三
A.组织结构
1.线程局部缓存ThreadCache
2.中央数据结构CentralHeap
1.大对象
2.小对象
C.测试
1.堆检查器
2.堆测量器
跨度
中央阵列
修改编译参数
./configure--disable-cpu-profiler --disable-heap-profiler --disable-heap-checker--enable-minimal –disable-dependency-tracking –disable-debugalloc
目的只生成最小的tcmalloc_minimal
./configure --disable-cpu-profiler --disable-heap-profiler --disable-heap-checker --disable-debugalloc --enable-minimal
目的只生成最小的tcmalloc_minimal
sudo echo "/usr/local/lib" > /etc/ld.so.conf.d/usr_local_lib.conf #如果没有这个文件,自己建一个 sudo /sbin/ldconfig
修改mysql服务的启动脚本mysqld_safe,在“#executing mysqld_safe”行后添加行:
exportLD_PRELOAD="/usr/local/lib/libtcmalloc.so"
目的是在启动mysql前,加载tcmalloc动态库。
将尺寸小于<=32K的对象,称之为“小对象”。
有划分为170个可分配的尺寸类别。
尺寸类别间隔:
较小的尺寸相差8字节;
较大的尺寸相差16字节;
再大一点的尺寸差32字节,如此类推。
最大的间隔(对于尺寸>=~2K的)是256字节。
一个线程缓存对每个尺寸类都包含了一个自由对象的单向链表。
当分配一个小对象时:
目的:我们将其大小映射到对应的尺寸类中。
第一步:查找当前线程的线程缓存中相应的自由列表。
第二步:
1.首先移走自由列表里的第一个对象;
2.再返回该对象;
当按照这个算法的时候,TCMalloc不会获取任何锁。这就可以极大提高分配的速度,因为锁/解锁操作在一个2.8GHzXeon上大约需要100纳秒的时间。
首先,从该尺寸类别的中央自由列表(中央自由列表是被所有线程共享的)取得一连串对象。
再将他们放入线程局部的自由列表。
将新获取的对象中的一个返回给应用程序。
大对象直接使用页级分配器(一个页是一个4K的对齐内存区域)从中央堆直接分配。
由此可以看出,一个大对象总是页对齐的并占据了整数个数的页。
结论:
1.从中央堆里直接分配;
2.使用的技术手段页级分配器;
3.一个大对象总是页对齐的;
4.一个大对象总是占据了整数个数的页;
5.页是一个4K大小的对齐内存区域;
联系:连续的一些页面可以被分割为一系列小对象。
区别:但是他们的大小都相同。例如,一个连续的页面(4K)可以被划分为32个128字节的对象。
连续的页面由一个“跨度”(Span
)对象来表示。
或者说跨度用来表示连续的页面。
一个跨度可以是已被分配或者是自由的。
如果是自由的,跨度则会是一个页面堆链表中的一个条目。
如果已被分配,它会是一个已经被传递给应用程序的大对象,或者是一个已经被分割成一系列小对象的一个页面。如果是被分割成小对象的,对象的尺寸类别会被记录在跨度中。
页面号索引的中央阵列可以用于找到某个页面所属的跨度。
也就是说,页面号有索引,索引保存在中央阵列,通过中央数组,可以找到页面的所属的跨度。
在一个32位的地址空间中,中央阵列由一个2层的基数树来表示,其中根包含了32个条目,每个叶包含了215个条目(一个32为地址空间包含了220个4K页面,所以这里树的第一层则是用25整除220个页面)。这就导致了中央阵列的初始内存使用需要128KB空间(215*4字节),看上去还是可以接受的。
在64位机器上,我们将使用一个3层的基数树。
页面的大小:4K= 4 * 210 = 212
一个32为地址空间寻址空间是:232
一个32为地址空间包含了232/ (4 * 210) = 220个 4K页面
当一个对象被解除分配时,我们先计算他的页面号并在中央阵列中查找对应的跨度对象。该跨度会告诉我们该对象是大是小,如果它是小对象的话尺寸类别是什么。如果是小对象的话,我们将其插入到当前线程的线程缓存中对应的自由列表中。如果线程缓存现在超过了某个预定的大小(默认为2MB),我们便运行垃圾收集器将未使用的对象从线程缓存中移入中央自由列表。
如果该对象是大对象的话,跨度会告诉我们该对象覆盖的页面的范围。假设该范围是[p,q]
。我们还会查找页面p-1
和页面q+1
对应的跨度。如果这两个相邻的跨度中有任何一个是自由的,我们将他们和[p,q]
的跨度接合起来。最后跨度会被插入到页面堆中合适的自由列表中。
当一个对象被释放的时候,
1.先计算出他的页面号;
2.然后去中央阵列中查找对应的跨度对象。该跨度会告诉我们对象是大对象,还是小对象;
3.如果是小对象,尺寸类别是什么;
4.我们将该类别,插入到当前线程的线程缓存中对应的自由列表中;
5.插入之后,如果线程缓存超过了某个预定的大小(默认为2MB),我们便运行垃圾收集器将未使用的对象从线程缓存中移入中央自由列表。