Solaris Slab Allocator–Magazines[2]

Thrashing

Wikipedia上的解释:Thrash(computer_science) 。我在这里就暂时叫做"抖动"吧。

首先从我的理解说一下为什么在CPU Layer和Slab Layer之间要有一个Depot Layer。从slab layer拿到的是slab, slab layer和cpu layer都没有把slab切分成magazine的逻辑。还有一点就是我们知道cpu layer中每个magazine中的slots越多(M越大),cache命中率就越高,代价是内存消耗越多。如何确定M的大小,也是放在depot layer来完成的。

现在的逻辑是这样的,depot layer中维护了一个full magazine list和一个empty magazine list。为什么depot不是只维护一个full_magazine_list呢?以cache_cpu[0]为例:

  • 如果当前的magazine是empty的,现在需要分配一个新的object,cache_cpu[0] 就从full magazine list中拿一个full magazine下来作为新的当前magazine,把该full magazine的第一个object分配出去。然后把empty的magazine还给depot layer。
  • 如果当前的magazine是full的,现在application free了一个object, 试想如果只有full_magazine_list的话,我们把新释放的object放在哪里呢?关键在于magazine其实是个逻辑容器,我们需要一个空的逻辑容器来容纳新释放的object。

但是,我们来看这样一种情况:

上图的情况就是thrashing。如果当前magazine为empty,需要alloc,于是造成一次cache miss,从depot load一个新的full magazine并还给depot一个empty magazine,然后接着free 两次,造成第二次miss cache,从depot load一个empty magazine并且还给depot一个full magazine。4次alloc/free,2次miss, miss rate=50%

解决的方法是这样的,注意到,在thrashing的时候,要么是用一个full magazine换回一个empty magazine;要么是用一个empty magazine换回一个full magazine。于是我们在cache_cpu中保存两个magazine,current_magazine和previous_magazine。

先paste一下cache_cpu的数据结构:
typedef struct umem_cpu_cache {
     mutex_t     cc_lock;     /* protects this cpu’s local cache */
     uint_t        cc_alloc;    /* allocations from this cpu */
     uint_t        cc_free;     /* frees to this cpu */
     umem_magazine_t    *cc_loaded ;      /* the currently loaded magazine */
     umem_magazine_t    *cc_ploaded;     /* the previously loaded magazine */
     int        cc_rounds ;     /* number of objects in loaded mag */
     int        cc_prounds ;    /* number of objects in previous mag */
     int        cc_magsize;    /* number of rounds in a full mag */
     int        cc_flags;        /* CPU-local copy of cache_flags */
#ifndef _LP64
     char        cc_pad[UMEM_CPU_PAD]; /* for nice alignment (32-bit) */
#endif
} umem_cpu_cache_t;

看分配alloc的算法, 在_umem_cache_alloc中
void * buf;
umem_cpu_cache_t* fmp;
retry:
umem_cpu_cache_t *ccp = UMEM_CPU_CACHE(…); //拿到当前CPU的cache_cpu数据结构
(void) mutex_lock(&ccp->cc_lock);                       //只负责该CPU上的同步
for(;;) {                                                           //其实这里的for是一个简单的状态机的实现
       //情况1:如果当前的magazine不为空
       if(ccp->cc_rounds > 0) {  //如果当前的magazine不为空
           buf = ccp->cc_loaded->mag_round[--ccp->cc_rounds]; //弹出一个object返回
           ccp->cc_alloc++;        //总分配数++
           (void)mutex_unlock(&ccp->cc_lock); //该CPU内共享的数据结构操作完成,解锁
           return (buf);              //返回当前magazine中的一个object
       }
       //情况2: 当前的magazine为空,但是previous magazine不为空
       if(ccp->cc_prounds > 0){  //previous magazine不为空
            umem_cpu_reload(ccp,ccp->cc_ploaded,ccp->cc_prounds); //交换当前magazine和prevous
            continue;                  //使用continue,使其在下次的尝试中落入情况1。
        }

        //情况3: current 和previous magazine都为空
        fmp = umem_depot_alloc(cp,&cp->cache_full); //问depot layer拿一个full magazine
        if(fmp !=NULL){ //如果depot layer 还有 full magazine
              if(ccp->cc_ploaded != NULL){
                    // 将previous empty magazine还给depot empty magazine
                    umem_depot_free(cp,&cp->cache_empty,ccp->cc_ploaded);
               }
               //交换 当前magazine和刚从depot拿到的full magazine
               umem_cpu_reload(ccp,fmp,ccp->cc_magsize);
               continue;            //使用continue,使其在下次的尝试中落入情况1。
        }
        //情况4:如果很不巧,连depot layer都没有full magazine了,拿只好动用slab layer了
        break;
}
(void)mutex_unlock(&ccp->cc_lock);
//下面的代码是处理情况4的…
……

释放的代码是类似的,我就不贴了。下面是原文中用于描述算法的伪代码,贴在这里作为参考。

有了这样的防抖动算法,cache的命中率最差也是1/M。而且我觉得这样的思路很经典,也很常用。下一篇笔记介绍在引入magazine layer以后object construction的策略。

 

 

你可能感兴趣的:(数据结构,object,cache,Solaris,layer,construction)