cache_t分析

292 阅读2分钟

cache_t结构

上一篇详细的介绍了类的结构,Class 是由ISA, superclass, cache, bits组成的结构体.除了cache,都已做了详细介绍,下面即将开始探寻cache_t之旅.

如下是cache_t的内部结构

struct cache_t {
    struct bucket_t *_buckets; // 8字节,*即是指针,指针占 8 字节
    mask_t _mask;  // 4字节,uint32_t mask_t,int 类型 4 字节
    mask_t _occupied; // 4字节,同上
}

bucket_t 结构体保存了 imp 以及 key,即 MethodCacheIMP _imp和 cache_key_t, 从中不难看出 cache_t 就是方法缓存

(lldb) x person.class
0x1000012b0: 89 12 00 00 01 80 1d 00 40 f1 af 00 01 00 00 00  ........@.......
0x1000012c0: d0 18 f4 00 01 00 00 00 03 00 00 00 03 00 00 00  ................
(lldb) p (cache_t *)0x1000012c0
(cache_t *) $1 = 0x00000001000012c0
(lldb) p *$1
(cache_t) $2 = {
  _buckets = 0x0000000100f418d0
  _mask = 3
  _occupied = 3
}
(lldb) p $2._buckets
(bucket_t *) $3 = 0x0000000100f418d0
(lldb) p *$3
(bucket_t) $4 = {
  _key = 4294971024
  _imp = 0x0000000100000c60 (LGTest`-[LGPerson sayHello] at LGPerson.m:13)
}

注意: cache_t第一次不走缓存.即 _imp = 0 ,当二次被调用时才会走缓存.(重启项目,再次运行,也是不走缓存).

下图即为 MachOview 中的体现.

image

如果多调用几个方法,_mask 就会增长.

cache_t内部是如何变化的,我们带着疑问,探索下去,我把源码的流程总结了一张图,方便理解

由此可见,cache_t内部变化是主要在于cache_fill--填充

static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver)
{
    cacheUpdateLock.assertLocked();

    // Never cache before +initialize is done
    if (!cls->isInitialized()) return;

    // Make sure the entry wasn't added to the cache by some other thread 
    // before we grabbed the cacheUpdateLock.
    if (cache_getImp(cls, sel)) return; //如果取到 imp,就不做缓存填充(判断是否可以命中缓存,通过 sel 找到 imp)

    cache_t *cache = getCache(cls);
    cache_key_t key = getKey(sel); //sel强转数值类型

    // Use the cache as-is if it is less than 3/4 full
    mask_t newOccupied = cache->occupied() + 1; //通知缓存,占用+1一个位置.来缓存当前方法
    mask_t capacity = cache->capacity(); //占用和现在的容量进行对比
    if (cache->isConstantEmptyCache()) { //空缓存表
        // Cache is read-only. Replace it.
        cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);
    }
    else if (newOccupied <= capacity / 4 * 3) { //占位小于容量桶子的3/4,正常存储,否则将扩容
        // Cache is less than 3/4 full. Use it as-is.
    }
    else {
        // Cache is too full. Expand it.
        cache->expand();//对缓存进行 2 倍扩容.
    }

    // Scan for the first unused slot and insert there.
    // There is guaranteed to be an empty slot because the 
    // minimum size is 4 and we resized at 3/4 full.
    bucket_t *bucket = cache->find(key, receiver);//找到脚标对应bucket_t 取值,空的bucket_t说明查找失败,则调用bad_cache.
    if (bucket->key() == 0) cache->incrementOccupied();//bucket_t 正确取值后,对应占位_occupied++
    bucket->set(key, imp);
}

注意:

Class中的Cache主要是为了在消息发送的过程中,进行方法的缓存,加快调用效率,其中使用了动态扩容的方法,容量达到最大值的3/4时,开始2倍扩容,扩容时会抹除之前的buckets,并且创建新的buckets代替,把最近一次临界点的imp和key缓存进来.