美文网首页
iOS-底层(8):类结构之cache_t结构分析

iOS-底层(8):类结构之cache_t结构分析

作者: 恍然如梦_b700 | 来源:发表于2020-09-18 18:42 被阅读0次

    今天我们来研究一下cache_t是什么

    前文书我们说过,在类的结构体中有个cache_t,我们来看看在类中的位置

    struct objc_class : objc_object {
        // Class ISA;
        Class superclass;
        cache_t cache;             // formerly cache pointer and vtable
        class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    
        class_rw_t *data() const {
            return bits.data();
        }
      //...此处省略很多坨代码
    }
    

    cache_t的结构

    struct cache_t {
    #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
        explicit_atomic<struct bucket_t *> _buckets;
        explicit_atomic<mask_t> _mask;
    #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
        explicit_atomic<uintptr_t> _maskAndBuckets;
        mask_t _mask_unused;
        
        // How much the mask is shifted by.
        static constexpr uintptr_t maskShift = 48;
        
        // Additional bits after the mask which must be zero. msgSend
        // takes advantage of these additional bits to construct the value
        // `mask << 4` from `_maskAndBuckets` in a single instruction.
        static constexpr uintptr_t maskZeroBits = 4;
        
        // The largest mask value we can store.
        static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
        
        // The mask applied to `_maskAndBuckets` to retrieve the buckets pointer.
        static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;
        
        // Ensure we have enough bits for the buckets pointer.
        static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS, "Bucket field doesn't have enough bits for arbitrary pointers.");
    #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
        // _maskAndBuckets stores the mask shift in the low 4 bits, and
        // the buckets pointer in the remainder of the value. The mask
        // shift is the value where (0xffff >> shift) produces the correct
        // mask. This is equal to 16 - log2(cache_size).
        explicit_atomic<uintptr_t> _maskAndBuckets;
        mask_t _mask_unused;
    
        static constexpr uintptr_t maskBits = 4;
        static constexpr uintptr_t maskMask = (1 << maskBits) - 1;
        static constexpr uintptr_t bucketsMask = ~maskMask;
    #else
    #error Unknown cache mask storage type.
    #endif
        
    #if __LP64__
        uint16_t _flags;
    #endif
        uint16_t _occupied;
    
    public:
        static bucket_t *emptyBuckets();
        
        struct bucket_t *buckets();
        mask_t mask();
        mask_t occupied();
        void incrementOccupied();
        void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
        void initializeToEmpty();
    
        unsigned capacity();
        bool isConstantEmptyCache();
        bool canBeFreed();
    
        //此处省略
    
    

    cache_t 的结构分三个架构来处理,macOS i386,模拟器x86,真机arm64
    下面的三个宏定义:

    • #define CACHE_MASK_STORAGE_OUTLINED 1 运行环境为macOS 或 模拟器
    • #define CACHE_MASK_STORAGE_HIGH_16 2 运行环境为64位真机
    • #define CACHE_MASK_STORAGE_LOW_4 3 运行环境为非64位真机等

    在真机环境下,由于为了进一步优化内存,将bucket和mask写到了一起,使用_maskAndBuckets,通过掩码来获取相应数据

    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
    
     //....省
    public:
        inline SEL sel() const { return _sel.load(memory_order::memory_order_relaxed); }
    
        inline IMP imp(Class cls) const {
            uintptr_t imp = _imp.load(memory_order::memory_order_relaxed);
            if (!imp) return nil;
    #if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_PTRAUTH
            SEL sel = _sel.load(memory_order::memory_order_relaxed);
            return (IMP)
                ptrauth_auth_and_resign((const void *)imp,
                                        ptrauth_key_process_dependent_code,
                                        modifierForSEL(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
        }
    
        template <Atomicity, IMPEncoding>
        void set(SEL newSel, IMP newImp, Class cls);
    };
    
    

    上面的源码我们可以得出:IMPSEL就存在bucket_t中。

    一 通过源码查找 sel和imp

    下一个断点


    image.png image.png

    根据,地址偏移,指针 和类中提供的方法等一系列操作,查看打印结果,当前断点前调用了两个方法,_occupied = 2,说明方法调用一次,就会缓存一次。

    二 脱离源码通过项目查找

    在工程中我们定义与cache_t结构类似的类,然后进行强转。

    #import "LGPerson.h"
    #import <objc/runtime.h>
    
    typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits
    
    struct lg_bucket_t {
        SEL _sel;
        IMP _imp;
    };
    
    struct lg_cache_t {
        struct lg_bucket_t * _buckets;
        mask_t _mask;
        uint16_t _flags;
        uint16_t _occupied;
    };
    
    struct lg_class_data_bits_t {
        uintptr_t bits;
    };
    
    struct lg_objc_class {
        Class ISA;
        Class superclass;
        struct lg_cache_t cache;             // formerly cache pointer and vtable
        struct lg_class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    };
    
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            LGPerson *p  = [LGPerson alloc];
            Class pClass = [LGPerson class];  // objc_clas
            [p say1];
            [p say2];
            [p say3];
            [p say4];
             
            // _occupied  _mask 是什么  cup - 1
            // 会变化 2-3 -> 2-7
            // bucket 会有丢失  重新申请
            // 顺序有点问题  哈希
      
            // 线索 :
            
            struct lg_objc_class *lg_pClass = (__bridge struct lg_objc_class *)(pClass);
            NSLog(@"%hu - %u",lg_pClass->cache._occupied,lg_pClass->cache._mask);
            for (mask_t i = 0; i<lg_pClass->cache._mask; i++) {
                // 打印获取的 bucket
                struct lg_bucket_t bucket = lg_pClass->cache._buckets[i];
                NSLog(@"%@ - %p",NSStringFromSelector(bucket._sel),bucket._imp);
            }
    
            
            NSLog(@"Hello, World!");
        }
        return 0;
    }
    
    
    • 我们先只调用两个方法 [p say1] 和[p say2] 我们看打印结果
    image.png
    • 打开say3和say4方法,再次运行看打印
    image.png

    看到上面的打印,我们不禁有些疑问

    • 1、_mask是什么?
    • 2、_occupied 是什么?
    • 3、为什么随着方法调用的增多,其打印的occupiedmask会变化?
    • 4、bucket数据为什么会有丢失的情况?,例如2-7中,只有say3、say4方法有函数指针
    • 5、2-7中say3、say4的打印顺序为什么是say4先打印,say3后打印,且还是挨着的,即顺序有问题?
    • 6、打印的cache_t中的_ocupied为什么是从2开始?

    乱序存储,我们一帮会想到哈希,那么是否和哈希有关,我们继续探索

    cache_t底层原理分析

    image.png

    实现:

    void cache_t::incrementOccupied() 
    {
        _occupied++;
    }
    
    

    我们看到cache_t中有一个incrementOccupied方法:增加Occupied
    全局搜索一下哪里调用了

    image.png

    在cache_t的插入方法里


    image.png
    void cache_t::insert(Class cls, SEL sel, IMP imp, id receiver)
    {
    #if CONFIG_USE_CACHE_LOCK
        cacheUpdateLock.assertLocked();
    #else
        runtimeLock.assertLocked();
    #endif
    
        ASSERT(sel != 0 && cls->isInitialized());
    
        // Use the cache as-is if it is less than 3/4 full
        mask_t newOccupied = occupied() + 1;
        unsigned oldCapacity = capacity(), capacity = oldCapacity;
        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 <= capacity / 4 * 3)) {
            // Cache is less than 3/4 full. Use it as-is.
        }
        else {
            capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
            if (capacity > MAX_CACHE_SIZE) {
                capacity = MAX_CACHE_SIZE;
            }
            reallocate(oldCapacity, capacity, true);
        }
    
        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 because the
        // minimum size is 4 and we resized at 3/4 full.
        do {
            if (fastpath(b[i].sel() == 0)) {
                incrementOccupied();
                b[i].set<Atomic, Encoded>(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));
    
        cache_t::bad_cache(receiver, (SEL)sel, cls);
    }
    
    

    首先开辟

    相关文章

      网友评论

          本文标题:iOS-底层(8):类结构之cache_t结构分析

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