类的cache_t底层探究

78 阅读5分钟

分析cache

image.png

cache

struct cache_t {
    static bucket_t *emptyBuckets(); // 清空
    static bucket_t *allocateBuckets(mask_t newCapacity); //创建
    static bucket_t *emptyBucketsForCapacity(mask_t capacity, bool allocate = true); //清空固定大小
    
    void insert(SEL sel, IMP imp, id receiver); // 存缓存的方法
    
    struct bucket_t *buckets() const; //核心数据类型
}

// insert方法
void cache_t::insert(SEL sel, IMP imp, id receiver) {
...
     if (slowpath(isConstantEmptyCache())) { //如果没有缓存
        // Cache is read-only. Replace it.
        if (!capacity) capacity = INIT_CACHE_SIZE;
        reallocate(oldCapacity, capacity, /* freeOld */false);
    }
    else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) {
        // Cache is less than 3/4 or 7/8 full. Use it as-is.
    }
#if CACHE_ALLOW_FULL_UTILIZATION
    else if (capacity <= FULL_UTILIZATION_CACHE_SIZE && newOccupied + CACHE_END_MARKER <= capacity) {
        // Allow 100% cache utilization for small buckets. Use it as-is.
    }
#endif
    else {
        capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
        if (capacity > MAX_CACHE_SIZE) {
            capacity = MAX_CACHE_SIZE;
        }
        reallocate(oldCapacity, capacity, true); // 创建缓存
    }
...
}
// 缓存肯定是没有的时候创建,那么这个`reallocate`方法肯定就是创建了
void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld)
{
    bucket_t *oldBuckets = buckets(); 
    bucket_t *newBuckets = allocateBuckets(newCapacity);

    // Cache's old contents are not propagated. 
    // This is thought to save cache memory at the cost of extra cache fills.
    // fixme re-measure this

    ASSERT(newCapacity > 0);
    ASSERT((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1);

    setBucketsAndMask(newBuckets, newCapacity - 1);
    
    if (freeOld) {
        collect_free(oldBuckets, oldCapacity);
    }
}

insert中发现了哈希bucket_t

    bucket_t *b = buckets();
    mask_t m = capacity - 1;
    mask_t begin = cache_hash(sel, m);
    mask_t i = begin;

    // Scan for the first unused slot and insert there.
    // There is guaranteed to be an empty slot.
    do {
        if (fastpath(b[i].sel() == 0)) { // 如果此位置的sel()为空,就进行赋值
            incrementOccupied(); // _occupied++, 使用数+1
            b[i].set<Atomic, Encoded>(b, sel, imp, cls()); //将sel和imp分别存入bucket的_sel和_imp中
            return;
        }
        // 如果当前位置的sel等于要插入的sel,则返回
        if (b[i].sel() == sel) {
            // The entry was added to the cache by some other thread
            // before we grabbed the cacheUpdateLock.
            return;
        }
        // 为防止哈希冲突,进行再哈希
    } while (fastpath((i = cache_next(i, m)) != begin));
//这个`while`循环是在`b`中找到第一个`sel为0`处,然后在这个位置将`sel和imp`分别存到`butket`的`_sel和_imp`中。`b[i].sel()`中,可断定`b[i]`是`butket`

存入缓存的核心是inset

void cache_t::insert(SEL sel, IMP imp, id receiver) { 
    ...
    //第一次使用时,没有缓存,则occupied为0
    mask_t newOccupied = occupied() + 1; 
    // 第一次没有缓存,所以 oldCapacity = 0,capacity = 0
    unsigned oldCapacity = capacity(), capacity = oldCapacity;
    if (slowpath(isConstantEmptyCache())) { // 第一次没有缓存,则进入创建方法
        // Cache is read-only. Replace it.
        if (!capacity) capacity = INIT_CACHE_SIZE; //INIT_CACHE_SIZE      = (1 << INIT_CACHE_SIZE_LOG2) 为4
        reallocate(oldCapacity, capacity, /* freeOld */false); // 0,4,false // 开辟内存
   }
   else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) {
     // CACHE_END_MARKER = 1
     // 第一次判断:newOccupied = 1,capacity = 4, 1+1 < 3*4/4,无其他操作
     // 第二次判断:newOccupied = 1+1=2,capacity = 4,2+1=3*4/4,无其他操作
     // 第三次判断:newOccupied = 2+1 = 3,capacity=4,3+1 > 3 * 4 / 4,不满足,走else
     // Cache is less than 3/4 or 7/8 full. Use it as-is.
    }
    #if CACHE_ALLOW_FULL_UTILIZATION //如果支持存满
    else if (capacity <= FULL_UTILIZATION_CACHE_SIZE && newOccupied + CACHE_END_MARKER <= capacity) {
        // Allow 100% cache utilization for small buckets. Use it as-is.
    } //-   在`small bucket`中,可以存满
#endif
    else {
    // -   当容量大于总容量`3/4`时,会进行扩容,总容量为之前的`2倍`,最大容量不能超过`2^15`
    capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
    // MAX_CACHE_SIZE = 1<<16 = 2^5 = 32
    if (capacity > MAX_CACHE_SIZE) {
        capacity = MAX_CACHE_SIZE;
    }
    reallocate(oldCapacity, capacity, true); //-   扩容后创建个新`bucket`,然后释放旧的`bucket`内存
}
 // 缓存
 bucket_t *b = buckets(); //拿到`bucket`地址
mask_t m = capacity - 1; // 计算能存储的位置
mask_t begin = cache_hash(sel, m); //拿到hash下标
mask_t i = begin;

// Scan for the first unused slot and insert there.
// There is guaranteed to be an empty slot.
do {
    if (fastpath(b[i].sel() == 0)) { // 如果此位置的sel()为空,就进行赋值
        incrementOccupied(); // _occupied++, 使用数+1
        b[i].set<Atomic, Encoded>(b, sel, imp, cls()); //将sel和imp分别存入bucket的_sel和_imp中
        return;
    }
    // 如果当前位置的sel等于要插入的sel,则返回
    if (b[i].sel() == sel) {
        // The entry was added to the cache by some other thread
        // before we grabbed the cacheUpdateLock.
        return;
    }
    // 为防止哈希冲突,进行再哈希
} while (fastpath((i = cache_next(i, m)) != begin));
/**
-   1.  根据内存订单`memory_order`拿到`bucket`地址
-   2.  根据`sel`和`mask`计算出起始位置
-   3.  通过遍历在`bucket`中寻找`sel为空`的位置,并在这个位置`对插入的sel和imp进行存储`,然后进行进行`_occupied`自增。
*、        
}

  • 第一次调用时,capacity = INIT_CACHE_SIZE = 4,再调用reallocate开辟:
//  第一次,oldCapacity:0,newCapacity:4,freeOld:false
void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld)
{
    bucket_t *oldBuckets = buckets(); //加载旧的buckets
    bucket_t *newBuckets = allocateBuckets(newCapacity); //创建新的

    // Cache's old contents are not propagated. 
    // This is thought to save cache memory at the cost of extra cache fills.
    // fixme re-measure this

    ASSERT(newCapacity > 0);
    ASSERT((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1);

    setBucketsAndMask(newBuckets, newCapacity - 1);
    
    if (freeOld) {
        collect_free(oldBuckets, oldCapacity);
    }
}

reallocate核心步骤是:

    1. allocateBuckets:开辟内存创建新bucket
    • bytesForCapacity(newCapacity)开辟内存
    • 计算出bucket地址,也就是最后一个bucket的地址,然后将bucket赋值,sel=1imp=newBuckets
    // 第一次,newCapacity:4
bucket_t *cache_t::allocateBuckets(mask_t newCapacity)
{
    // Allocate one extra bucket to mark the end of the list.
    // This can't overflow mask_t because newCapacity is a power of 2.
    bucket_t *newBuckets = (bucket_t *)calloc(bytesForCapacity(newCapacity), 1); // 根据计算出的内存,创建bucket

    bucket_t *end = endMarker(newBuckets, newCapacity); //根据新bucket起始位置,获取最后一个butket的地址

#if __arm__
    // End marker's sel is 1 and imp points BEFORE the first bucket.
    // This saves an instruction in objc_msgSend.
    end->set<NotAtomic, Raw>(newBuckets, (SEL)(uintptr_t)1, (IMP)(newBuckets - 1), nil);
#else
    // End marker's sel is 1 and imp points to the first bucket.
    // 最后一个 bucket赋值:sel = 1,imp = newBuckets
    end->set<NotAtomic, Raw>(newBuckets, (SEL)(uintptr_t)1, (IMP)newBuckets, nil);
#endif
    
    if (PrintCaches) recordNewCache(newCapacity); //记录缓存

    return newBuckets;
}

size_t cache_t::bytesForCapacity(uint32_t cap)
{
    return sizeof(bucket_t) * cap; //根据容量,计算内存
}

#if CACHE_END_MARKER // 模拟器
bucket_t *cache_t::endMarker(struct bucket_t *b, uint32_t cap)
{
    return (bucket_t *)((uintptr_t)b + bytesForCapacity(cap)) - 1; //根据新bucket大小,
}
    1. setBucketsAndMask:存储_maybeMask,设置_occupied_bucketsAndMaybeMask存储新bucket
      1. _bucketsAndMaybeMask存储newBuckets,根据架构不同存储newBuckets时的key不同
      1. _maybeMask存储newMask
      1. 存储完_occupied置为0
// 第一次 `newMask` 为3
void cache_t::setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask)
{
    // objc_msgSend uses mask and buckets with no locks.
    // It is safe for objc_msgSend to see new buckets but old mask.
    // (It will get a cache miss but not overrun the buckets' bounds).
    // It is unsafe for objc_msgSend to see old buckets and new mask.
    // Therefore we write new buckets, wait a lot, then write new mask.
    // objc_msgSend reads mask first, then buckets.

#ifdef __arm__
    // ensure other threads see buckets contents before buckets pointer
    mega_barrier();

    _bucketsAndMaybeMask.store((uintptr_t)newBuckets, memory_order_relaxed);

    // ensure other threads see new buckets before new mask
    mega_barrier();

    _maybeMask.store(newMask, memory_order_relaxed);
    _occupied = 0;
#elif __x86_64__ || i386
    // ensure other threads see buckets contents before buckets pointer
    _bucketsAndMaybeMask.store((uintptr_t)newBuckets, memory_order_release);

    // ensure other threads see new buckets before new mask
    _maybeMask.store(newMask, memory_order_release);
    _occupied = 0;
#else
#error Don't know how to do setBucketsAndMask on this architecture.
#endif
}

    1. collect_free:是否释放旧内存。
void cache_t::collect_free(bucket_t *data, mask_t capacity)
{
#if CONFIG_USE_CACHE_LOCK
    cacheUpdateLock.assertLocked();
#else
    runtimeLock.assertLocked();
#endif

    if (PrintCaches) recordDeadCache(capacity);

    _garbage_make_room (); // 创建回收站
    garbage_byte_size += cache_t::bytesForCapacity(capacity); //获取回收的`bucket`大小,并加到garbage_byte_size上
    garbage_refs[garbage_count++] = data;
    cache_t::collectNolock(false);
}
// 创建回收站,如果回收站满了会重新分配`2倍`空间:_garbage_make_room
static void _garbage_make_room(void)
{
     static int first = 1;

     // Create the collection table the first time it is needed
     if (first)
     {
         // 第一次调用创建回收站内存
         first = 0;
         garbage_refs = (bucket_t**)malloc(INIT_GARBAGE_COUNT * sizeof(void *));
         garbage_max = INIT_GARBAGE_COUNT; //设置最大存储量
     }

     // Double the table if it is full
     // 如果存储满了,就重新分配内存,最大的内存为之前的2倍
     else if (garbage_count == garbage_max)
     {
         garbage_refs = (bucket_t**)realloc(garbage_refs, garbage_max * 2 * sizeof(void *));
         garbage_max *= 2;
     }
}
// -   2.  `garbage_refs`:将当前回收`bucket`放在回收站`上一个回收后`的位置上。
//  -   3.  `collectNolock`:清空数据,回收内存

总结inset流程

image.png

cache整体流程图

image.png