TCMalloc源码阅读(二)--线程局部缓存ClassSize分析
2013-11-06 20:07
671 查看
TCMalloc小对象分配机制
首先我们回顾下TCMalloc文档的小对象分配机制。文档中说明TCMalloc给每个线程都保存一个缓存池,缓存池里有各种大小的内存对象。小内存分配过程如下:1. 将要分配的大小映射到对应的对齐对象。
2. 在当前线程的局部缓存中查找该对齐对象链表。
3. 如果该链表不为空,删除链表第一个节点并返回给调用者。
问题
1. 小对象是如何划分的?2. 对于任意一个小于kMaxSize的size是如何映射到某一种缓存对象上?
本文将通过分析源代码来弄清楚这两个问题。
SizeMap分析
在do_malloc函数中有如下两行代码:size_t cl = Static::sizemap()->SizeClass(size);
size = Static::sizemap()->class_to_size(cl);
不难理解,这两行代码就是size映射到它最接近的缓存对象上。接下来继续探究SizeClass(size_t)和class_to_(size_t)两个函数,这两个函数在common.h文件中,代码如下:
[cpp] view
plaincopy
class SizeMap
{
private:
... //其他暂时不关心的
//-------------------------------------------------------------------
// Mapping from size to size_class and vice versa
//-------------------------------------------------------------------
// Sizes <= 1024 have an alignment >= 8. So for such sizes we have an
// array indexed by ceil(size/8). Sizes > 1024 have an alignment >= 128.
// So for these larger sizes we have an array indexed by ceil(size/128).
//
// We flatten both logical arrays into one physical array and use
// arithmetic to compute an appropriate index. The constants used by
// ClassIndex() were selected to make the flattening work.
//
// Examples:
// Size Expression Index
// -------------------------------------------------------
// 0 (0 + 7) / 8 0
// 1 (1 + 7) / 8 1
// ...
// 1024 (1024 + 7) / 8 128
// 1025 (1025 + 127 + (120<<7)) / 128 129
// ...
// 32768 (32768 + 127 + (120<<7)) / 128 376
static const int kMaxSmallSize = 1024;
static const size_t kClassArraySize =
((kMaxSize + 127 + (120 << 7)) >> 7) + 1;
unsigned char class_array_[kClassArraySize];
// Compute index of the class_array[] entry for a given size
static inline int ClassIndex(int s) {
ASSERT(0 <= s);
ASSERT(s <= kMaxSize);
const bool big = (s > kMaxSmallSize);
const int add_amount = big ? (127 + (120<<7)) : 7;
const int shift_amount = big ? 7 : 3;
return (s + add_amount) >> shift_amount;
}
// Mapping from size class to max size storable in that class
size_t class_to_size_[kNumClasses];
public:
inline int SizeClass(int size) {
return class_array_[ClassIndex(size)];
}
...//暂时不关心的
// Mapping from size class to max size storable in that class
inline size_t class_to_size(size_t cl) {
return class_to_size_[cl];
}
}
原来SizeClass只是返回class_array_数组中的某个元素,这元素的索引由ClassIndex函数计算。ClassIndex的计算逻辑也很简单,size<=1024的按8字节对齐,size>1024的按128字节对齐。这样对于[0,1,2,...,1024]就映射成了[0,1,...,128],对于[1025,1026,...,kMaxSize]就会映射成[9,10,...,2048].
我们需要将这两数组按照原来size的顺序合并成一个数组。1024映射成了128,按理,1025应该映射成129. 为达到该目的,我们将后面的一个数组全部加上120,这样两个数组就可以合并成[0,1,...,128,129,...,2168]。
如此就不难理解当size<=1024时size=(size+7)/8,位运算表达式为:(size+7)>>3. 当size>1024时,size=(size+127+120*128)/128, 位运算表达式为:(size+127+(120<<7))>>7
ClassIndex(size_t)算是搞清楚了,但是从代码中可以看出ClassIndex计算出来的只是class_array_的索引值。class_array_里存储的是class_to_size_的索引,class_to_size_的大小为kNumClass,kNumClass的定义如下:
[cpp] view
plaincopy
#if defined(TCMALLOC_LARGE_PAGES)
static const size_t kPageShift = 15;
static const size_t kNumClasses = 78;
#else
static const size_t kPageShift = 13;
static const size_t kNumClasses = 86;
#endif
class_arrar_和class_to_size_的初始化代码如下:
[cpp] view
plaincopy
// Initialize the mapping arrays
void SizeMap::Init() {
// Do some sanity checking on add_amount[]/shift_amount[]/class_array[]
...
// Compute the size classes we want to use
int sc = 1; // Next size class to assign
int alignment = kAlignment;
CHECK_CONDITION(kAlignment <= 16);
for (size_t size = kAlignment; size <= kMaxSize; size += alignment) {
alignment = AlignmentForSize(size);
...
if (sc > 1 && my_pages == class_to_pages_[sc-1]) {
// See if we can merge this into the previous class without
// increasing the fragmentation of the previous class.
const size_t my_objects = (my_pages << kPageShift) / size;
const size_t prev_objects = (class_to_pages_[sc-1] << kPageShift)
/ class_to_size_[sc-1];
if (my_objects == prev_objects) {
// Adjust last class to include this size
class_to_size_[sc-1] = size;
continue;
}
}
// Add new class
class_to_pages_[sc] = my_pages;
class_to_size_[sc] = size;
sc++;
}
...
// Initialize the mapping arrays
int next_size = 0;
for (int c = 1; c < kNumClasses; c++) {
const int max_size_in_class = class_to_size_[c];
for (int s = next_size; s <= max_size_in_class; s += kAlignment) {
class_array_[ClassIndex(s)] = c;
}
next_size = max_size_in_class + kAlignment;
}
// Double-check sizes just to be safe
...
// Initialize the num_objects_to_move array.
...
}
代码中还包含了其他的成员初始化,这些目前都不是我们关心的,为更清楚了解class_array_和class_to_size_是如何初始化的,我们删除不必要的代码。class_to_size_保存了每一类对齐内存对象的大小,各类大小的关系大致为:class_to_size_
=class_to_size_[n-1]+AlignmentForSize(class_to_size_[n-1])。代码中还有一些对齐类的大小是需要调整的,这里先不考虑。
AlignmentForSize函数的代码实现如下:
[cpp] view
plaincopy
static inline int LgFloor(size_t n) {
int log = 0;
for (int i = 4; i >= 0; --i) {
int shift = (1 << i);
size_t x = n >> shift;
if (x != 0) {
n = x;
log += shift;
}
}
ASSERT(n == 1);
return log;
}
int AlignmentForSize(size_t size) {
int alignment = kAlignment;
if (size > kMaxSize) {
// Cap alignment at kPageSize for large sizes.
alignment = kPageSize;
} else if (size >= 128) {
// Space wasted due to alignment is at most 1/8, i.e., 12.5%.
alignment = (1 << LgFloor(size)) / 8;
} else if (size >= 16) {
// We need an alignment of at least 16 bytes to satisfy
// requirements for some SSE types.
alignment = 16;
}
// Maximum alignment allowed is page size alignment.
if (alignment > kPageSize) {
alignment = kPageSize;
}
CHECK_CONDITION(size < 16 || alignment >= 16);
CHECK_CONDITION((alignment & (alignment - 1)) == 0);
return alignment;
}
先说下LgFloor函数,该函数的功能是返回size值的最高位1的位置。
例如,LgFloor(8)=3, LgFloor(9)=3, LgFloor(10)=3,...,LgFloor(16)=4,...
通过AlignmentForSize函数,我们不难得知class_to_size_的分类规则大致如下:
size在[16,128]之间按16字节对齐,size在[129,256*1024]之间按(2^(n+1)-2^n)/8对齐,n为7~18,
即[129,130,...,256*1024]会被映射为[128+16,128+2*16,...,128+8*16,256+32,256+2*32,...,256+8*32],超过256*1024以上的按页对齐。
因为ClassIndex计算出来的结果还是太密集了,因此需要通过class_array_来索引到其真正映射到的对齐类。
总结
1. 线程局部缓存将[0~256*1024]范围的内存按如下规则对齐:size<16,8字节对齐,对齐结果[8,16]
size在[16,128)之间,按16字节对齐,对齐结果[32,48,...,128]
size在[128,256*1024),按(2^(n+1)-2^n)/8对齐,对齐结果[128+16,128+2*16,...,128+8*16,256+32,256+2*32,...,256+8*32]
2. 用class_to_size_保存所有对齐结果。
3. class_array_保存ClassIndex(size)到class_to_size_的映射关系。
4. ClassIndex(size)通过简单的字节对齐算法计算class_array_的索引。
相关文章推荐
- TCMalloc源码阅读(二)--线程局部缓存ClassSize分析
- tcmalloc源码阅读(三)---ThreadCache分析之线程局部缓存
- tcmalloc源码阅读(三)---ThreadCache分析之线程局部缓存
- JVM源码分析之线程局部缓存TLAB
- 线程源码阅读分析,持续更新中。。。
- TCMalloc源码阅读(四)--ThreadCache分析之空闲内存链表
- 4、Volley解析(二),源码的深入分析一,缓存线程和网络请求线程
- TCMalloc源码阅读(四)--ThreadCache分析之空闲内存链表
- 第二人生的源码分析(四十三)虚拟文件系统线程
- (转载)Memcached源码分析(线程模型)
- nginx 源码分析阅读笔记-进程管理
- 卷二 Dalvik与Android源码分析 第二章 进程与线程 2.1 Dalvik虚拟机的进程创建机制 图书版试读--请勿转发
- shiro源码分析篇4:自定义缓存
- jdk源码分析--了解class文件
- 蔡军生先生第二人生的源码分析(四十)创建多个工作线程
- Redis源码分析-TCMalloc
- TCMalloc:线程缓存的Malloc [Webkit有其应用]
- Scala深入浅出进阶经典 第68讲:Scala并发编程原生线程Actor、Cass Class下的消息传递和偏函数实战解析及其在Spark中的应用源码解析
- Android源码分析:Java的Media Scanner层(阅读笔记)
- [原创] jQuery源码分析-13 CSS操作-CSS-类样式-addClass+removeClass+toggleClass+hasClass