内存管理(三)tcmalloc1 内存分配及源码剖析

来源:互联网 发布:linux强行关闭程序 编辑:程序博客网 时间:2024/06/08 02:20

    本来打算花一天时间看看tcmalloc就算结束了。但是在网上找博客的时候发现,100个人有101钟不同的解释,完全没有公论。一怒之下,剖源码!

 

        tcmalloc是对ptmalloc的升级版。和ptmalloc相比,tcmalloc对于小块内存的速度要比ptmalloc快得多,并且相对于每一个内存块分配都需要8Btcmalloc对于细节的优化ptmalloc做得好。tcmalloc对于页的理解和主流不太一样,他是以8K作为一个页。由于tcmalloc是第三方库,所以要使用的话就得自行下载安装。下面谈谈tcmalloc的分配。

 

        tcmalloc将内存请求分为大内存和小内存。大内存>32K,小内存<=32K。如果要优化多线程的内存分配,tcmalloc为每一个线程创建了一个私有的cache,在处理小内存的请求时会先从这一个cache来查找内存块。由于这是每个线程私有的空间,所以在处理申请的时候就不需要进行加锁解锁操作。处理大内存的时候会从中央堆来分配。下面挨个来讨论。

 

小内存分配与回收

 

        tcmalloc内的维护了86个分配器,这些分配器通过一个链表free list来管理。但是他们相邻差并不是类似ptmalloc的相等,而是以8,16,32这样的数列分开。在分配内存的时候,他会先把大小映射到对应的空间集合,接着从链表里找出比申请内存大的最小空间,找到以后直接返回给调用者。如果没找到,就会向中央堆申请内存,然后填充到对应的集合里,接着放到线程本地free list,接着就修改free list的最大可能长度这个属性。每次分配都会增大最大可能长度,最后会返回一个给调用者。如果中央堆也没有内存了,就会向系统申请一个页,切割一块比申请内存大的最小空间出来分配,并且记录。

 

Free list的结构如下:



    前面提到,线程会在cache里找不到的情况下向中央堆里申请内存。在从中央堆申请内存的函数里,我们可以看到。函数为了保证线程安全会先上锁,上了锁以后就会从缓存区ECEntry里看有没有符合条件的内存。如果有,就会返回。如果没有,就会调用FretchFromSpanSafe函数来从某个span里获得内存并返回。

 

从线程free list 获得内存的代码如下:

inline void * ThreadCache:: Allocate(size_t size, size_t cl )

{ //并没有加锁

ASSERT(size <= kMaxSize);

ASSERT(size == Static:: sizemap()->ByteSizeForClass (cl));

FreeList* list = &list_[ cl];

if (list ->empty())

{

return FetchFromCentralCache (cl, size); //如果从free list找不到,那么就去中央堆里找

}

size_ -= size ;

 return list ->Pop(); //把最上面那个给调用者

}

 

在线程自身的cache里分配是不用加锁的,但是如果需要向中央堆来申请的话。由于中央堆是共享的,所以要加自旋锁。

 

从中央堆里获得内存的代码如下:

int CentralFreeList ::RemoveRange( void **start , void ** end, int N) //返回的是获得内存块的数目,N代表需要多少个内存块

{

ASSERT( N > 0);

lock_. Lock(); //上锁

if ( N == Static ::sizemap()-> num_objects_to_move(size_class_ ) && used_slots_ >0) //缓存区里有足够的内存块

{

int slot = --used_slots_;

ASSERT(slot >= 0);

TCEntry *entry = &tc_slots_[ slot]; //TCEntry缓存数列里查找

* start = entry ->head;

* end = entry ->tail;

lock_.Unlock ();

return N ;

}

……

Int result = 0; //准备直接从span里取,取的多少就把result置为多少

Int *start = NULL; //先置为空,如果找不到,就直接返回空

Int *end = NULL;

tail = FetchFromSpansSafe(); //找不到就只能去span里获得了

if ( tail != NULL )

{

head = tail ;

result = 1;

while (result < N)

{

void *t = FetchFromSpans();

if (!t )break;

result++;

}

}

lock_. Unlock(); //解锁

*start = head;

*end = tail;

return result;

}

Span标示一段连续的内存页,他可以作为节点和其他span串起来,也可以把内存页化为一个个objects供分配给小内存。他的定义如下:

Struct Span

{

PageID start; //starting page number

Length length; //number of pages in span

Span* next; //use when in link list

Span* prev; //use when in link list

Void* objects; //linked list of free objects

}

 

如果之前查找缓存区失败,就会尝试从span获得内存:

void* CentralFreeList ::FetchFromSpansSafe()

{

void * t = FetchFromSpans (); //先试着从没分配完的页里获得内存

if (! t)

{

Populate(); //从页堆(也就是多个页连在一起的heap)申请内存

t = FetchFromSpans ();

}

return t;

}

先试着从FetchFromSpans获取:

void* CentralFreeList ::FetchFromSpans()

{

if ( tcmalloc::DLL_IsEmpty (&nonempty_))

  return NULL ; //如果说 非空的span(也就是被使用过但是内存没用完的span)没有了,直接返回

Span* span = nonempty_ .next;

ASSERT( span->objects != NULL);

span-> refcount++; //找到的这个span还能用,先在引用数上+1

void* result = span ->objects;

span-> objects = *(reinterpret_cast <void**>( result));

if ( span->objects == NULL) //如果这次引用完了就没空间了,那就放到一个empty里去

{

tcmalloc::DLL_Remove (span);

  tcmalloc::DLL_Prepend (&empty_, span);

Event(span , 'E', 0);

}

counter_--;

return result;

}

 

如果span里找不到,那就只能去pageheap里看看了:

 

void CentralFreeList ::Populate()

{

lock_. Unlock();  //先把中央堆的锁给解了,用不着

const size_t npages = Static:: sizemap()->class_to_pages (size_class_);

Span* span;

{

SpinLockHolder h(Static ::pageheap_lock()); //pageheap锁了

span = Static ::pageheap()-> New(npages );

if (span ) Static:: pageheap()->RegisterSizeClass (span, size_class_);

}

if ( span == NULL ) //如果span是空的话就得在日志文件里记录错误返回了。

{

……

}

ASSERT( span->length == npages);

for (int i = 0; i < npages; i ++)

{

Static::pageheap ()->CacheSizeClass( span->start + i, size_class_);  //把页切割成size_class的大小

}

……

char* limit = ptr + (npages << kPageShift);

int num =0;

while ( ptr + size <= limit)

{

……num++; //累加获得的块的大小

}

tcmalloc:: DLL_Prepend(&nonempty_ , span);//放到缓存区

++num_spans_;

counter_ += num;

}

 

    如果一些都失败了,就会考虑free的内存和unmmaped的内存可能有足够多的小块页内存。于是会把free的内存全部释放掉。释放过程起始也会有和邻近的页内存合并的过程。这样就达到了吧所有可用的小块内存合并的内存。如果还是找不到(强迫症)就会扩充堆的大小(PageHeap::GrowHeap),GrowHeap从系统获取指定大小的内存(以页对齐)

 

大内存分配

    如果申请的内存大于32K,就会向中央堆里申请内存。中央堆里的内存单位是页,也是通过一个数组来管理。一共有256个元素。前255是代表着从1页到255页。最后一项代表着需要更多页面的小空间。如果依旧不够用,就会向系统申请。具体的方法函数见上文。

 

 

 

 


0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 手机淘宝购物车不小心删除了怎么办 上淘宝网图片文字不清晰怎么办 淘宝店铺低消费人群占比多怎么办 微信只能在应用商城里面打开怎么办 淘宝买了特价商品店家不发货怎么办 红米5a装不下卡怎么办 红米3s流量太慢怎么办 红米3s触屏失灵怎么办 红米note的4g信号差怎么办 电信4g网速慢怎么办红米手机 红米3电信4g信号不好怎么办 红米note3无法连接4g怎么办 红米2a手机开不了机怎么办 红米2a不支持微信运动怎么办 红米2a开不了机怎么办 红米2a突然开不了机怎么办 苹果手机刷机刷到一半没电了怎么办 红米5手机死屏了怎么办 小米2a手机开不了机怎么办 小米2a长时间没用开不了机怎么办 红米手机玩游戏太卡怎么办 红米2a无限重启怎么办 红米3s像素好差怎么办 红米4x后摄像头进水了怎么办 红米手机卡死了又不能拆电池怎么办 红米1s格式化输入法没了怎么办 红米5a锁屏密码怎么办 红米1s手机开不开机怎么办 红米2a忘记解锁密码怎么办 红米手机忘了解锁图案怎么办 账户密码忘记了手机号不用了怎么办 手机电池用完了冲不进去电怎么办 华为手机电池一体的想扣电池怎么办 苹果微信支付显示需要验证码怎么办 天猫极速退货上门取件预约满怎么办 手机锁死了忘记魅族账号密码怎么办 魅蓝e升级系统开不了机了怎么办 京东第三方店铺显示关闭怎么办 派派怎么提现朋友不够怎么办 派派邀请30个好友才能提现怎么办 派派更换手机号后提现时怎么办