探索Cache_t

Cache_t 的整體分析
Cache_t的源碼

objc/objc-runtime-new源碼下查找結(jié)構(gòu)體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;
    ...//省略,掩碼用處
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    explicit_atomic<uintptr_t> _maskAndBuckets;
    ...//省略
#else
#error Unknown cache mask storage type.
#endif

#if __LP64__
    uint16_t _flags;
#endif
    uint16_t _occupied;
}

那么CACHE_MASK_STORAGE的判斷是什么意義呢?查看CACHE_MASK_STORAGE宏。

arm64表示真機(jī),LP64表示64位結(jié)構(gòu)

#if defined(__arm64__) && __LP64__
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16
#elif defined(__arm64__) && !__LP64__
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4  
#else
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED//模擬器,MacOS
#endif
lldb獲取cache_t
cache_t 緩存buckets集合

cache_t下有一個(gè)struct bucket_t *buckets();,這是一個(gè)bucket_t結(jié)構(gòu)體集合的指針,我們可以像訪問數(shù)組的方式一下,訪問buckets內(nèi)的內(nèi)每一個(gè)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
...//省略
public:
    inline SEL sel() const { return _sel.load(memory_order::memory_order_relaxed); }

    inline IMP imp(Class cls) const {
    ...//省略
    }
    ...//省略
}
Cache_t的結(jié)構(gòu)圖
Cache_t結(jié)構(gòu).png
脫離源碼環(huán)境調(diào)試分析
完整代碼
typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits

struct oc_bucket_t {
    SEL _sel;
    IMP _imp;
};

struct oc_cache_t {
    struct oc_bucket_t * _buckets;
    mask_t _mask;
    uint16_t _flags;
    uint16_t _occupied;
};

struct oc_class_data_bits_t {
    uintptr_t bits;
};

struct oc_objc_class {
    Class ISA;
    Class superclass;
    struct oc_cache_t cache;             // formerly cache pointer and vtable
    struct oc_class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
};

void logClass(Class class){
        struct oc_objc_class *lg_pClass = (__bridge struct oc_objc_class *)(class);
        NSLog(@"%hu - %u",lg_pClass->cache._occupied,lg_pClass->cache._mask);
        for (mask_t i = 0; i<lg_pClass->cache._mask; i++) {
            // 打印獲取的 bucket
            struct oc_bucket_t bucket = lg_pClass->cache._buckets[I];
            NSLog(@"%@ - %p",NSStringFromSelector(bucket._sel),bucket._imp);
        }

}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p  = [Person alloc];
        Class pClass = [Person class];  // objc_clas
        [p say1];
        [p say2];
       
        logClass(pClass);
        [p say3];
        [p say4];
        logClass(pClass);
        
        [p say5];
        [p say6];
        logClass(pClass);
     
   
    }
    return 0;
}


輸出結(jié)果
  • 調(diào)用兩個(gè)方法時(shí):

    兩種不同的方法.png
  • 調(diào)用四個(gè)方法時(shí):
四種不同的方法.png
Cache_t原理分析
insert方法分析
  1. Cache_t中找到了 void incrementOccupied();函數(shù),對(duì)_occupied進(jìn)行自增
  1. 全局搜索incrementOccupied()函數(shù),發(fā)現(xiàn)只在objc-cache文件下cache_tinsert方法有調(diào)用,
incrementOccupied調(diào)用.png
  1. 查看insert方法,我們發(fā)現(xiàn)進(jìn)入該方法后,就會(huì)對(duì)當(dāng)前的occupied進(jìn)行+1賦值給新的變量 newOccupied,

    • 如果當(dāng)前buckets為空,則進(jìn)行重新開辟空間reallocate,reallocate中調(diào)用setBucketsAndMask進(jìn)行初始化,_occupied等于0,INIT_CACHE_SIZE4.

      void cache_t::setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask)
      {
      
      #ifdef __arm__
          // ensure other threads see buckets contents before buckets pointer
          mega_barrier();
          _buckets.store(newBuckets, memory_order::memory_order_relaxed);
          
          // ensure other threads see new buckets before new mask
          mega_barrier();
          
          _mask.store(newMask, memory_order::memory_order_relaxed);
          _occupied = 0;
      #elif __x86_64__ || i386
          // ensure other threads see buckets contents before buckets pointer
          _buckets.store(newBuckets, memory_order::memory_order_release);
          
          // ensure other threads see new buckets before new mask
          _mask.store(newMask, memory_order::memory_order_release);
          _occupied = 0;
      #else
      #error Don't know how to do setBucketsAndMask on this architecture.
      #endif
      }
      
      
    • 如果新增的值 小于容量的 3/4,則什么也不做

    • 否則進(jìn)行擴(kuò)容,重新開辟空間,_occupied等于0。

    • 最后緩存方法,并對(duì)_occupied進(jìn)行自增。

    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);
    }
    
    
Cache_t流程圖

附上一個(gè)流程圖


Cooci 關(guān)于Cache_t原理分析圖.png
_occupied是什么?

_occupied為分配的內(nèi)存中已經(jīng)存儲(chǔ)了sel-imp的的個(gè)數(shù)

_mask是什么?

_mask是指掩碼數(shù)據(jù)mask 等于capacity - 1

為什么buckets會(huì)有丟失?

reallocate擴(kuò)容時(shí),是將原有的內(nèi)存全部清除了,再重新申請(qǐng)了內(nèi)存導(dǎo)致

為什么順序有問題?

哈希算法計(jì)算下標(biāo),下標(biāo)是隨機(jī)的,并不是固定的.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容