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

优化tcmalloc

上文中已经确定tcmalloc在Sylixos下效率低的原因是使用了pthread key机制。实际使用中,malloc和free都必须调用pthread_getspecific函数来获取线程私有的ThreadCache,因此需要对pthread_getspecific进行改造以提升效率。
改造有2个方向,第一是修改Sylixos内核,改造pthread key的实现机制。第二是在tcmalloc库中,在使用pthread_getspecific函数的地方进行改造。第一种方式影响范围不仅局限于tcmalloc库,同时影响内核代码,因此暂时采用第二种方法进行改造。

修改tcmalloc库

tcmalloc库中共创建了2个pthread key

pthread_key_t ThreadCache::heap_key;
pthread_key_t thread_disable_counter_key;

第一个heap_key就是我们这里关心的ThreadCache相关key,由于不止一个key的存在,因此对程序的改造只能对heap_key产生影响,且保证不影响其他key的正常工作。
改造的重点是要解决所有线程私有数据统一管理时需要锁的问题,想要取消锁,则需要提前为每一个线程独立分配一个指向ThreadCache的指针。对于tcmalloc来说,用户什么时候创建线程销毁线程,tcmalloc并不知道,因为该数据原本是由pthread来管理,tcmalloc只负责向pthread写入和获取数据指针。由于用户线程管理的不确定性,只能提前为所有线程ID都分配一个指针,在Sylixos内核中创建线程时,为线程生成ID的方法如下

#define _OBJECT_THREAD          	1
#define LW_CFG_PROCESSOR_NUMBER         1
ulIdTemp = _MakeObjectId(_OBJECT_THREAD, 
                        LW_CFG_PROCESSOR_NUMBER, 
                        ptcb->TCB_usIndex);

在这里插入图片描述
_MakeObjectId的第一个参数对应于图片中CLASS,为固定值,第二个参数对应于NODE,也为固定值,第三个参数是需要的ID,该参数是递增的。可以确定线程ID的范围是16位数既0-65535,在32位系统下tcmalloc为每个线程ID创建一个指针需要4*65536=256KB,因此当一个进程链接tcmalloc库运行时会增加256KB内存的开销。具体在实现时,主要是增加了2个函数,用于替代原始的perftools_pthread_getspecific和perftools_pthread_setspecific,为了不影响tcamalloc库的其他程序,这2个函数只在通过heap_key获取线程私有数据时生效,这样可以保证tcmalloc库其他功能仍然采用原有机制正常运作

static volatile void *pvValPtrMap[1<<16];

void *perftools_pthread_getspecific_fast(pthread_key_t key) {
    return (void *)pvValPtrMap[API_ThreadIdSelf() & 0xFFFF];
}

int perftools_pthread_setspecific_fast(pthread_key_t key, void *val) {
    int ret = pthread_setspecific(key, val);
    pvValPtrMap[API_ThreadIdSelf() & 0xFFFF] = val;
    return ret;
}

这2个函数主要逻辑是,在使用perftools_pthread_setspecific设置线程私有数据时候不仅通过pthread_setspecific存放数据,同时将数据放入该线程ID的指针中,在使用perftools_pthread_getspecific获取线程私有数据时,则绕过效率低的pthread_getspecific函数,改为直接从线程ID指针中获取,从而大大加快了读取速度,因为该ID指针是和线程唯一映射的,不会产生多线程竞争问题,也避免了锁的问题。
观察发现在以上2个函数中,实际还存在一个稍微影响效率的点,既API_ThreadIdSelf()函数,进入Sylixos内核中可以看到实现如下

LW_OBJECT_HANDLE  API_ThreadIdSelf (VOID)
{
    PLW_CLASS_TCB   ptcbCur;
    
    if (LW_CPU_GET_CUR_NESTING()) {/* 不能在中断中调用 */
        _DebugHandle(__ERRORMESSAGE_LEVEL, "called from ISR.\r\n");
        _ErrorHandle(ERROR_KERNEL_IN_ISR);
        return  (0);
    }
    
    LW_TCB_GET_CUR_SAFE(ptcbCur);
    
    return  (ptcbCur->TCB_ulId);
}

API_ThreadIdSelf的第一步排除在中断中调用,第二步获取线程控制块并得到线程ID。在排除中断调用的过程中需要关中断开中断,这里多少有一些开销,且tcmalloc调用可以确定不在中断中,所以可以去掉该判断。基于此在内核中增加一个函数API_ThreadIdSelfIgnIrq

LW_OBJECT_HANDLE  API_ThreadIdSelfIgnIrq (VOID)
{
    PLW_CLASS_TCB   ptcbCur;
    
    LW_TCB_GET_CUR_SAFE(ptcbCur);
    
    return  (ptcbCur->TCB_ulId);
}

将perftools_pthread_getspecific_fast和perftools_pthread_setspecific_fast中的API_ThreadIdSelf用API_ThreadIdSelfIgnIrq替代后,即完成了对tcmalloc的优化工作。
经过本文第一章使用的测试方法测试后,得到tcmalloc运行时间数据如下:
Testing…(step0-malloc, step1-copy, step2-free)
Thread 3(tid:0x040100D7): malloc size 16Byte result:
Thread 3(tid:0x040100D7): step 0, min 11us, max 14us, avg 11us
Thread 3(tid:0x040100D7): step 1, min 7us, max 9us, avg 7us
Thread 3(tid:0x040100D7): step 2, min 12us, max 15us, avg 12us
Thread 3(tid:0x040100D7): malloc size 64Byte result:
Thread 3(tid:0x040100D7): step 0, min 11us, max 14us, avg 11us
Thread 3(tid:0x040100D7): step 1, min 30us, max 35us, avg 30us
Thread 3(tid:0x040100D7): step 2, min 12us, max 17us, avg 12us
Thread 3(tid:0x040100D7): malloc size 128Byte result:
Thread 3(tid:0x040100D7): step 0, min 11us, max 14us, avg 11us
Thread 3(tid:0x040100D7): step 1, min 55us, max 66us, avg 55us
Thread 3(tid:0x040100D7): step 2, min 12us, max 14us, avg 12us
Thread 3(tid:0x040100D7): malloc size 256Byte result:
Thread 3(tid:0x040100D7): step 0, min 11us, max 15us, avg 11us
Thread 3(tid:0x040100D7): step 1, min 111us, max 131us, avg 111us
Thread 3(tid:0x040100D7): step 2, min 12us, max 17us, avg 12us
Thread 3(tid:0x040100D7): malloc size 512Byte result:
Thread 3(tid:0x040100D7): step 0, min 11us, max 16us, avg 11us
Thread 3(tid:0x040100D7): step 1, min 221us, max 263us, avg 222us
Thread 3(tid:0x040100D7): step 2, min 12us, max 14us, avg 12us
Thread 3(tid:0x040100D7): malloc size 1024Byte result:
Thread 3(tid:0x040100D7): step 0, min 11us, max 17us, avg 12us
Thread 3(tid:0x040100D7): step 1, min 440us, max 539us, avg 443us
Thread 3(tid:0x040100D7): step 2, min 12us, max 17us, avg 12us
Thread 3(tid:0x040100D7): malloc size 4096Byte result:
Thread 3(tid:0x040100D7): step 0, min 12us, max 18us, avg 12us
Thread 3(tid:0x040100D7): step 1, min 449us, max 590us, avg 461us
Thread 3(tid:0x040100D7): step 2, min 12us, max 17us, avg 12us
Thread 3(tid:0x040100D7): malloc size 16384Byte result:
Thread 3(tid:0x040100D7): step 0, min 12us, max 17us, avg 12us
Thread 3(tid:0x040100D7): step 1, min 446us, max 530us, avg 449us
Thread 3(tid:0x040100D7): step 2, min 14us, max 23us, avg 15us
Thread 3(tid:0x040100D7): malloc size 40960Byte result:
Thread 3(tid:0x040100D7): step 0, min 12us, max 97us, avg 13us
Thread 3(tid:0x040100D7): step 1, min 448us, max 694us, avg 450us
Thread 3(tid:0x040100D7): step 2, min 14us, max 430us, avg 15us
Thread 3(tid:0x040100D7): malloc size 131072Byte result:
Thread 3(tid:0x040100D7): step 0, min 42us, max 1694us, avg 60us
Thread 3(tid:0x040100D7): step 1, min 448us, max 588us, avg 462us
Thread 3(tid:0x040100D7): step 2, min 42us, max 1073us, avg 59us

对比之前未优化的tcmalloc,可以发现任何内存大小的申请下,都提升巨大,可见互斥锁对效率影响非常大,另外相比其他系统说对来说,SylixOS下的一些底层接口(pthread key)以及互斥锁的性能存在一些效率问题,有待优化。
对比ptmalloc,在40KB以下内存申请时,效率提高了2-5倍左右,在128KB内存申请时,效率提高了100倍以上。且tcmalloc在小内存的申请上不存在线程的竞争的问题,也不会产生ptmalloc的2ms锁延时问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值