tcmalloc在sylixos上的移植和优化(2)

tcmalloc效率低的原因分析

上文中tcmalloc的效率相比ptmalloc,除了大内存(大于40KB)申请效率较高,其他情况都远低于ptmalloc,本文采用代码跟踪方式查找效率低下的原因所在。
当应用层调用malloc时,实际调用的是tc_malloc函数

void* tc_malloc(size_t size) PERFTOOLS_NOTHROW {
  return malloc_fast_path<tcmalloc::malloc_oom>(size);
}
static void * malloc_fast_path(size_t size) {
  if (PREDICT_FALSE(!base::internal::new_hooks_.empty())) {
    return tcmalloc::dispatch_allocate_full<OOMHandler>(size);
  }
  ThreadCache *cache = ThreadCache::GetFastPathCache();
  if (PREDICT_FALSE(cache == NULL)) {
    return tcmalloc::dispatch_allocate_full<OOMHandler>(size);
  }
  uint32 cl;
  if (PREDICT_FALSE(!Static::sizemap()->GetSizeClass(size, &cl))) {
    return tcmalloc::dispatch_allocate_full<OOMHandler>(size);
  }
  size_t allocated_size = Static::sizemap()->ByteSizeForClass(cl);
  if (PREDICT_FALSE(!cache->TryRecordAllocationFast(allocated_size))) {
    return tcmalloc::dispatch_allocate_full<OOMHandler>(size);
  }
  return CheckedMallocResult(cache->Allocate(allocated_size, cl, OOMHandler));
}

经过调试发现,最大时间延迟出现在malloc_fast_path函数中ThreadCache *cache = ThreadCache::GetFastPathCache(),调用此函数会产生5us的延时,接着跟踪此函数

inline ThreadCache* ThreadCache::GetFastPathCache() {
#ifndef HAVE_TLS
  return GetCacheIfPresent();
#else
  return threadlocal_data_.fast_path_heap;
#endif
}

HAVE_TLS在config.h头文件已经定义,GetFastPathCache函数只是返回threadlocal_data_.fast_path_heap这样一个变量,从表面看返回一个变量不应该耗时5us

 struct ThreadLocalData {
   ThreadCache* fast_path_heap;
   ThreadCache* heap;
   bool use_emergency_malloc;
 };
 static __thread ThreadLocalData threadlocal_data_
   CACHELINE_ALIGNED ATTR_INITIAL_EXEC;

从上面代码看出threadlocal_data_.fast_path_heap确实只是结构体中的一个变量,但定义threadlocal_data_时的一句修饰语句在这里起到了决定性作用。
__thread的修饰符:表示每一个线程有一份独立的实体,每一个线程都不会干扰。这句修饰也是tcmalloc的精髓,既看似全局变量,实际是线程私有数据,__thread在底层实现上依赖于pthread的key机制,所以实际上使用__thread和使用pthread的key机制实现是一样的,在tcmalloc的源码中也体现了这一点,当去掉HAVE_TLS宏定义,其底层确实使用了pthread的key机制

inline ThreadCache* ThreadCache::GetThreadHeap() {
#ifdef HAVE_TLS
  return threadlocal_data_.heap;
#else
  return reinterpret_cast<ThreadCache *>(
      perftools_pthread_getspecific(heap_key_));
#endif
}

因每一次的malloc和free都需要通过pthread_getspecific获取本地的ThreadCache,到这里可以确定效率低的问题出在pthread_getspecific函数中,继续跟踪内核的源码实现,以下代码删除了函数中部分无关内容

void *pthread_getspecific (pthread_key_t  key)
{
  void   *pvalue = LW_NULL;
  __pthreadDataGet(key, &pvalue);
  return  (pvalue);
}

static INT  __pthreadDataGet (long  lId, void  **ppvData)
{
  iHash = __PX_KEY_THREAD_HASH(ulMe);
  __PX_KEY_LOCK(pkeyn);
  for (plineTemp  = pkeyn->PKEYN_plineKeyHeader[iHash];
    plineTemp != LW_NULL;
    plineTemp  = _list_line_get_next(plineTemp)) {
    pkeyd = (__PX_KEY_DATA *)plineTemp;
    if (pkeyd->PKEYD_ulOwner == ulMe) {
       *ppvData = pkeyd->PKEYD_pvData;
       break;
    }
  }
  __PX_KEY_UNLOCK(pkeyn); 
  return  (ERROR_NONE);
}

#define __PX_KEY_LOCK(pkeyn)        API_SemaphoreMPend(pkeyn->PKEYN_ulMutex, LW_OPTION_WAIT_INFINITE)
#define __PX_KEY_UNLOCK(pkeyn)      API_SemaphoreMPost(pkeyn->PKEYN_ulMutex)

pthread_getspecific函数在内核中最终调用__pthreadDataGet,该函数的流程:
1,内核使用Hash散列方式分散了链表数据以提高查找速度,先根据线程ID找到key链表头。
2,因为各个线程的本地私有数据都保存在key链表中,因此需要调用__PX_KEY_LOCK加锁,实际该宏使用互斥信号量加锁。
3,从链表中遍历查找和当前thread ID匹配的线程本地私有数据。
4,调用__PX_KEY_UNLOCK释放互斥信号量。

到此发现,内核的pthread key的实现机制,和tcmalloc原始设计思想是有冲突的,tcmalloc想通过一个线程本地私有数据的访问不影响其他线程来提高效率,但是该本地私有数据在内核中是通过一个共享链表来实现。因此,效率低的最主要原因是内核中对链表的加解锁操作,其次是对链表的遍历,后面的tcmalloc优化也将重点解决此问题。待续…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值