美文网首页
objc_class底层cache_t详解

objc_class底层cache_t详解

作者: linc_ | 来源:发表于2022-05-04 19:43 被阅读0次

    cache_t 结构解析

    类的底层原理探索 中我们了解了objc_class中存储了isa,superClass,cache,bits,今天我们来看下cache的作用和底层实现。

    cache结构

    image.png
    这个结构并不能看出来cache的作用,所以我们通过内存偏移打印一下cache的内容
    image.png

    我们打印的信息和其数据结构一致,但是缓存的内容我们还是不知道,我们只能从源码中继续往下看。

    image.png

    原码中有一个insert方法,应该是用来存入数据的。

    进入这个insert函数来看一下


    image.png

    我们注意到insert方法的参数有SEL,IMP,receiver,并且将这些参数放在了bucket中,我们来验证一下


    获取bucket_t的内存内容

    bucket_t看到了sel和imp,但是输出的内容又看不懂了,来看看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
    
        // Compute the ptrauth signing modifier from &_imp, newSel, and cls.
        uintptr_t modifierForSEL(bucket_t *base, SEL newSel, Class cls) const {
            return (uintptr_t)base ^ (uintptr_t)newSel ^ (uintptr_t)cls;
        }
    
        // Sign newImp, with &_imp, newSel, and cls as modifiers.
        uintptr_t encodeImp(UNUSED_WITHOUT_PTRAUTH bucket_t *base, IMP newImp, UNUSED_WITHOUT_PTRAUTH SEL newSel, Class cls) const {
            if (!newImp) return 0;
    #if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_PTRAUTH
            return (uintptr_t)
                ptrauth_auth_and_resign(newImp,
                                        ptrauth_key_function_pointer, 0,
                                        ptrauth_key_process_dependent_code,
                                        modifierForSEL(base, newSel, cls));
    #elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR
            return (uintptr_t)newImp ^ (uintptr_t)cls;
    #elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_NONE
            return (uintptr_t)newImp;
    #else
    #error Unknown method cache IMP encoding.
    #endif
        }
    
    public:
        static inline size_t offsetOfSel() { return offsetof(bucket_t, _sel); }
        inline SEL sel() const { return _sel.load(memory_order_relaxed); }
    
    #if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR
    #define MAYBE_UNUSED_ISA
    #else
    #define MAYBE_UNUSED_ISA __attribute__((unused))
    #endif
        inline IMP rawImp(MAYBE_UNUSED_ISA objc_class *cls) const {
            uintptr_t imp = _imp.load(memory_order_relaxed);
            if (!imp) return nil;
    #if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_PTRAUTH
    #elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR
            imp ^= (uintptr_t)cls;
    #elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_NONE
    #else
    #error Unknown method cache IMP encoding.
    #endif
            return (IMP)imp;
        }
    
        inline IMP imp(UNUSED_WITHOUT_PTRAUTH bucket_t *base, Class cls) const {
            uintptr_t imp = _imp.load(memory_order_relaxed);
            if (!imp) return nil;
    #if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_PTRAUTH
            SEL sel = _sel.load(memory_order_relaxed);
            return (IMP)
                ptrauth_auth_and_resign((const void *)imp,
                                        ptrauth_key_process_dependent_code,
                                        modifierForSEL(base, sel, cls),
                                        ptrauth_key_function_pointer, 0);
    #elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR
            return (IMP)(imp ^ (uintptr_t)cls);
    #elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_NONE
            return (IMP)imp;
    #else
    #error Unknown method cache IMP encoding.
    #endif
        }
    
        inline void scribbleIMP(uintptr_t value) {
            _imp.store(value, memory_order_relaxed);
        }
    
        template <Atomicity, IMPEncoding>
        void set(bucket_t *base, SEL newSel, IMP newImp, Class cls);
    };
    

    我们看到 调用sel()函数可以返回SEL,调用imp(UNUSED_WITHOUT_PTRAUTH bucket_t *base, Class cls)可以返回IMP,于是就可以试试在内存上获取到的bucket_t去调用这两个函数:


    调用sel()和imp()函数 cache_t

    cache_t扩容

    那方法能存多少呢?存满了又是如何扩容呢?buckets是如何扩容的?为什么我没调用class和respondsToSelector方法它们就缓存到了buckets里面了?
    看下cache_t的insert函数的实现代码

    void cache_t::insert(SEL sel, IMP imp, id receiver)
    {   
        runtimeLock.assertLocked();
    
        // Never cache before +initialize is done
        if (slowpath(!cls()->isInitialized())) {
            return;
        }
    
        if (isConstantOptimizedCache()) {
            _objc_fatal("cache_t::insert() called with a preoptimized cache for %s",
                        cls()->nameForLogging());
        }
    
    #if DEBUG_TASK_THREADS
        return _collecting_in_critical();
    #else
    #if CONFIG_USE_CACHE_LOCK
        mutex_locker_t lock(cacheUpdateLock);
    #endif
    
        ASSERT(sel != 0 && cls()->isInitialized());
    
        // Use the cache as-is if until we exceed our expected fill ratio.
        mask_t newOccupied = occupied() + 1; // 第一次insert的时候occupied()即_occupied会是0,newOccupied会是1
        // capacity的值就是buckets的长度
        unsigned oldCapacity = capacity(), capacity = oldCapacity;
        // 如果cache为空,则分配 arm64下长度为2 x86_64下长度为4的buckets,reallocate里无需释放老buckets
        if (slowpath(isConstantEmptyCache())) {
            // Cache is read-only. Replace it.
            // 给容量附上初始值,x86_64为4,arm64为2
            if (!capacity) capacity = INIT_CACHE_SIZE;
            reallocate(oldCapacity, capacity, /* freeOld */false);
        }
        // 在arm64下,缓存的大小 <= buckets长度的7/8  不扩容
        // 在x86_64下,缓存的大小 <= buckets长度的3/4  不扩容
        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 // 只有arm64才需要走这个判断
        // 在arm64下,buckets的长度 < = 8 时,不扩容
        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 { // 除却上面的逻辑,就是扩容逻辑了
            // 对当前容量的2倍扩容,并且如果扩容后容量大小 大于 一个最大阈值,则设置为这个最大值
            capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
            if (capacity > MAX_CACHE_SIZE) {
                capacity = MAX_CACHE_SIZE;
            }
            // 创建新的扩容后的buckets,释放旧的bukets
            reallocate(oldCapacity, capacity, true);
        }
    
        bucket_t *b = buckets(); // 获取buckets数组指针
        mask_t m = capacity - 1; // m是buckets的长度-1
        mask_t begin = cache_hash(sel, m);// 通过hash计算出要插入的方法在buckets上的起始位置(begin不会超过buckets的长度-1)
        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)) { // 当前hash计算出来的buckets在i的位置它有没有值,如果没有值就去存方法
                incrementOccupied();
                b[i].set<Atomic, Encoded>(b, sel, imp, cls());
                return;
            }
            if (b[i].sel() == sel) { // 当前hash计算出来的buckets在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)); // 如果计算出来的起始位置i存在hash冲突的话,就通过cache_next去改变i的值(增大i)
    
        bad_cache(receiver, (SEL)sel);
    #endif // !DEBUG_TASK_THREADS
    }
    

    从代码上看,第一次空了会进行初始化capacity的长度INIT_CACHE_SIZE.


    image.png
    image.png

    在arm64架构下开辟一个长度为2的桶子,在x86_64架构下开辟长度为4的桶子

    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.
        }
    

    在arm64架构下如果缓存大于等于桶子长度的7/8,在x86_64架构下缓存大小大于等于桶子长度的3/4 则什么也不干。
    在arm64架构下,当桶子的长度小于等于8的时候什么也不干
    当缓存长度大于(系统设定的)默认最大值就等于默认最大值
    其他情况下需要扩容,扩容的大小为2倍。

    所以我们就明白了为什么之前调用了方法之后会什么没有找到,arm64下,初始值为2,当第1个方法缓存的时候,则要进行两倍扩容为4,并且需要清除旧桶。所以instanceMethod在刚进来扩容的时候就被清除掉了,也就找不到了。而前面我们说到class方法和responseToSelector方法是我们在调试的时候通过lldb调用产生的。

    相关文章

      网友评论

          本文标题:objc_class底层cache_t详解

          本文链接:https://www.haomeiwen.com/subject/oellyrtx.html