TCMalloc源码阅读(二)--线程局部缓存ClassSize分析

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 plain copy
  1. class SizeMap  
  2. {  
  3. private:  
  4.     ... //其他暂时不关心的  
  5.     //-------------------------------------------------------------------  
  6.     // Mapping from size to size_class and vice versa  
  7.     //-------------------------------------------------------------------  
  8.   
  9.   
  10.     // Sizes <= 1024 have an alignment >= 8.  So for such sizes we have an  
  11.     // array indexed by ceil(size/8).  Sizes > 1024 have an alignment >= 128.  
  12.     // So for these larger sizes we have an array indexed by ceil(size/128).  
  13.     //  
  14.     // We flatten both logical arrays into one physical array and use  
  15.     // arithmetic to compute an appropriate index.  The constants used by  
  16.     // ClassIndex() were selected to make the flattening work.  
  17.     //  
  18.     // Examples:  
  19.     //   Size       Expression                      Index  
  20.     //   -------------------------------------------------------  
  21.     //   0          (0 + 7) / 8                     0  
  22.     //   1          (1 + 7) / 8                     1  
  23.     //   ...  
  24.     //   1024       (1024 + 7) / 8                  128  
  25.     //   1025       (1025 + 127 + (120<<7)) / 128   129  
  26.     //   ...  
  27.     //   32768      (32768 + 127 + (120<<7)) / 128  376  
  28.     static const int kMaxSmallSize = 1024;  
  29.     static const size_t kClassArraySize =  
  30.       ((kMaxSize + 127 + (120 << 7)) >> 7) + 1;  
  31.     unsigned char class_array_[kClassArraySize];  
  32.   
  33.   
  34.     // Compute index of the class_array[] entry for a given size  
  35.     static inline int ClassIndex(int s) {  
  36.         ASSERT(0 <= s);  
  37.         ASSERT(s <= kMaxSize);  
  38.         const bool big = (s > kMaxSmallSize);  
  39.         const int add_amount = big ? (127 + (120<<7)) : 7;  
  40.         const int shift_amount = big ? 7 : 3;  
  41.         return (s + add_amount) >> shift_amount;  
  42.     }  
  43.   
  44.   
  45.     // Mapping from size class to max size storable in that class  
  46.     size_t class_to_size_[kNumClasses];  
  47. public:  
  48.     inline int SizeClass(int size) {  
  49.     return class_array_[ClassIndex(size)];  
  50.   }  
  51.   
  52.   
  53.   ...//暂时不关心的  
  54.   // Mapping from size class to max size storable in that class  
  55.   inline size_t class_to_size(size_t cl) {  
  56.     return class_to_size_[cl];  
  57.   }  
  58. }  

原来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 plain copy
  1. #if defined(TCMALLOC_LARGE_PAGES)  
  2. static const size_t kPageShift  = 15;  
  3. static const size_t kNumClasses = 78;  
  4. #else  
  5. static const size_t kPageShift  = 13;  
  6. static const size_t kNumClasses = 86;  
  7. #endif  

class_arrar_和class_to_size_的初始化代码如下:

[cpp]  view plain copy
  1. // Initialize the mapping arrays  
  2. void SizeMap::Init() {  
  3.   // Do some sanity checking on add_amount[]/shift_amount[]/class_array[]  
  4.   ...  
  5.   // Compute the size classes we want to use  
  6.   int sc = 1;   // Next size class to assign  
  7.   int alignment = kAlignment;  
  8.   CHECK_CONDITION(kAlignment <= 16);  
  9.   for (size_t size = kAlignment; size <= kMaxSize; size += alignment) {  
  10.     alignment = AlignmentForSize(size);  
  11.     ...  
  12.     if (sc > 1 && my_pages == class_to_pages_[sc-1]) {  
  13.       // See if we can merge this into the previous class without  
  14.       // increasing the fragmentation of the previous class.  
  15.       const size_t my_objects = (my_pages << kPageShift) / size;  
  16.       const size_t prev_objects = (class_to_pages_[sc-1] << kPageShift)  
  17.                                   / class_to_size_[sc-1];  
  18.       if (my_objects == prev_objects) {  
  19.         // Adjust last class to include this size  
  20.         class_to_size_[sc-1] = size;  
  21.         continue;  
  22.       }  
  23.     }  
  24.   
  25.   
  26.     // Add new class  
  27.     class_to_pages_[sc] = my_pages;  
  28.     class_to_size_[sc] = size;  
  29.     sc++;  
  30.   }  
  31.   ...  
  32.   // Initialize the mapping arrays  
  33.   int next_size = 0;  
  34.   for (int c = 1; c < kNumClasses; c++) {  
  35.     const int max_size_in_class = class_to_size_[c];  
  36.     for (int s = next_size; s <= max_size_in_class; s += kAlignment) {  
  37.       class_array_[ClassIndex(s)] = c;  
  38.     }  
  39.     next_size = max_size_in_class + kAlignment;  
  40.   }  
  41.   
  42.   
  43.   // Double-check sizes just to be safe  
  44.   ...  
  45.   
  46.   // Initialize the num_objects_to_move array.  
  47.   ...  
  48. }  

代码中还包含了其他的成员初始化,这些目前都不是我们关心的,为更清楚了解class_array_和class_to_size_是如何初始化的,我们删除不必要的代码。class_to_size_保存了每一类对齐内存对象的大小,各类大小的关系大致为:class_to_size_[n]=class_to_size_[n-1]+AlignmentForSize(class_to_size_[n-1])。代码中还有一些对齐类的大小是需要调整的,这里先不考虑。

AlignmentForSize函数的代码实现如下:

[cpp]  view plain copy
  1. static inline int LgFloor(size_t n) {  
  2.   int log = 0;  
  3.   for (int i = 4; i >= 0; --i) {  
  4.     int shift = (1 << i);  
  5.     size_t x = n >> shift;  
  6.     if (x != 0) {  
  7.       n = x;  
  8.       log += shift;  
  9.     }  
  10.   }  
  11.   ASSERT(n == 1);  
  12.   return log;  
  13. }  
  14. int AlignmentForSize(size_t size) {  
  15.   int alignment = kAlignment;  
  16.   if (size > kMaxSize) {  
  17.     // Cap alignment at kPageSize for large sizes.  
  18.     alignment = kPageSize;  
  19.   } else if (size >= 128) {  
  20.     // Space wasted due to alignment is at most 1/8, i.e., 12.5%.  
  21.     alignment = (1 << LgFloor(size)) / 8;  
  22.   } else if (size >= 16) {  
  23.     // We need an alignment of at least 16 bytes to satisfy  
  24.     // requirements for some SSE types.  
  25.     alignment = 16;  
  26.   }  
  27.   // Maximum alignment allowed is page size alignment.  
  28.   if (alignment > kPageSize) {  
  29.     alignment = kPageSize;  
  30.   }  
  31.   CHECK_CONDITION(size < 16 || alignment >= 16);  
  32.   CHECK_CONDITION((alignment & (alignment - 1)) == 0);  
  33.   return alignment;  
  34. }  


先说下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_的索引。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值