类的原理分析 cache

117 阅读2分钟

之前文章已经看过了bits的内容,对property、method、ivar进行了基础的了解,现在来进行cache的基本分析。

之前用lldb对类的结构进行分析,已经了解了如何获取到bits,同样进行内存平移可以查看cache的内容。

image.png

struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask;
#if __LP64__
            uint16_t                   _flags;
#endif
            uint16_t                   _occupied;
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;
    };
   ......
    static bucket_t *emptyBuckets();
    static bucket_t *allocateBuckets(mask_t newCapacity);
    static bucket_t *emptyBucketsForCapacity(mask_t capacity, bool allocate = true);
    static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);
    void bad_cache(id receiver, SEL sel) __attribute__((noreturn, cold));
    ......
    void insert(SEL sel, IMP imp, id receiver);
    void copyCacheNolock(objc_imp_cache_entry *buffer, int len);
    void destroy();
    void eraseNolock(const char *func);
}

缓存无非是对于类中的方法属性等进行缓存,通过其中的的方法看大部分都是对bucket_t的相关方法,查看bucket_t的结构

struct bucket_t {
private:
    // IMP-first is better for arm64e ptrauth and no worse for arm64.
    // SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
    explicit_atomic<uintptr_t> _imp;
    explicit_atomic<SEL> _sel;
#else
    explicit_atomic<SEL> _sel;
    explicit_atomic<uintptr_t> _imp;
#endif
......

终于我们看到了我们要找的SEL和IMP,接下来验证方法的缓存

获取内容查看结构体中提供的方法struct bucket_t *buckets() const;

image.png

但是当前的内容中并没有看到我们预期的结果,因为当前我们还没有进行方法的调用,我们对方法进行一次调用,然后再看当前的内容

image.png

调用bucket_t的sel方法

image.png

同样的方式查看IMP

image.png

当方法被调用后,才会缓存到cache中,为了提高方法查找效率。

方法缓存的过程

插入方法

void insert(SEL sel, IMP imp, id receiver);

void cache_t::insert(SEL sel, IMP imp, id receiver)
{
    runtimeLock.assertLocked();

    ......

    // Use the cache as-is if until we exceed our expected fill ratio.
    mask_t newOccupied = occupied() + 1;
    unsigned oldCapacity = capacity(), capacity = oldCapacity;
    if (slowpath(isConstantEmptyCache())) {
        // 第一次cache为空的时候初始化
        // INIT_CACHE_SIZE = 1 << 2
        // Cache is read-only. Replace it.
        if (!capacity) capacity = INIT_CACHE_SIZE; 
        // 调用setBucketsAndMask(newBuckets, newCapacity - 1)给_bucketsAndMaybeMask复制,_occupoed = 0
        reallocate(oldCapacity, capacity, /* freeOld */false);
    }
    else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) {
        /*
        // 判断扩容条件,如果大于75%进行扩容,如果不大于,进行正常操作
        static inline mask_t cache_fill_ratio(mask_t capacity) {
            return capacity * 3 / 4;
        }
        */
        // 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 {
        // 如果超出75%的容量,进行两倍扩容
        capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
        if (capacity > MAX_CACHE_SIZE) {
            capacity = MAX_CACHE_SIZE;
        }
        // 当调用四个方法时,会发现只剩下最后调用的一个方法,原理就在这
        // 当调用第四个方法时需要扩容,当前方法传入参数true
        // 会对旧的内存进行清空操作 collect_free(oldBuckets, oldCapacity);
        /*
        
        */
        reallocate(oldCapacity, capacity, true);
    }

    bucket_t *b = buckets();   // 获取到buckets数组
    mask_t m = capacity - 1;
    mask_t begin = cache_hash(sel, m); // 通过对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)) {
            // 如果没有缓存当前方法,_occupied ++,并将方法进行缓存
            incrementOccupied();
            b[i].set<Atomic, Encoded>(b, sel, imp, cls());
            return;
        }
        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)); // 防止哈希冲突进行再次哈希

    bad_cache(receiver, (SEL)sel);
#endif // !DEBUG_TASK_THREADS
}

调试过程中比较重要的但我们并没有用到的还有_bucketsAndMaybeMask,实际上缓存方法的重要信息都存放到_bucketsAndMaybeMask指向的内容,其中的Value对应的就是buckets,在buckets的实现中, bucketsMask~0ul所以就是取地址

struct bucket_t *cache_t::buckets() const
{
    uintptr_t addr = _bucketsAndMaybeMask.load(memory_order_relaxed);
    return (bucket_t *)(addr & bucketsMask);
}