iOS中的Cache_t流程詳解

前言:

在最近學(xué)習(xí)過(guò)程中我們知道一個(gè)類的結(jié)構(gòu)的定義,以及一個(gè)對(duì)象的alloc的執(zhí)行流程。初探底層的源碼。經(jīng)過(guò)最新開(kāi)源的objc781我們知道,類的結(jié)構(gòu)中重要的成員有

  • Class ISA

  • Class superclass

  • cache_t cache

  • class_data_bits_t bits

類的定義代碼如下

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();
    }
     ......... //還包括很多數(shù)據(jù)和方法等

在之前的博客中我們?cè)鴮?duì) isaclass_data_bits_t 已經(jīng)進(jìn)行了一個(gè)自我學(xué)習(xí)和總結(jié)的過(guò)程,接下來(lái)我們就針對(duì)中很重要的cache_t再次深入進(jìn)行一個(gè)自我學(xué)習(xí)和總結(jié)。希望通過(guò)這樣的學(xué)習(xí)、幫助自己更深刻的理解類的緩存工作原理

一、cache_t 的環(huán)境結(jié)構(gòu)

一個(gè)類的結(jié)構(gòu)cache_t大致流程如下:截圖來(lái)自Cooci老師的課件

cache_t的層級(jí)分布

我們接下來(lái)看看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;

    static constexpr uintptr_t maskShift = 48;

    static constexpr uintptr_t maskZeroBits = 4;

    static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;

    static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;

    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

    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;

1在虛擬模擬器中的結(jié)構(gòu)

當(dāng)我們編譯我們代碼中的時(shí)候,相關(guān)的環(huán)境已經(jīng)就確定了;所以我們能看到模擬器和macOS中的結(jié)構(gòu)是

#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
 explicit_atomic<struct bucket_t *> _buckets;
 explicit_atomic<mask_t> _mask;
#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();

再次進(jìn)入_buckets 能看到 在模擬器和macOS中的結(jié)構(gòu)

  explicit_atomic<SEL> _sel;
  explicit_atomic<uintptr_t> _imp;

2在真機(jī)調(diào)試中的結(jié)構(gòu)

#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic<uintptr_t> _maskAndBuckets;
    mask_t _mask_unused;
#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();

再次進(jìn)入_buckets 能看到 在模擬器和macOS中的結(jié)構(gòu)

    explicit_atomic<uintptr_t> _imp;
    explicit_atomic<SEL> _sel;

這就是cache_t在各個(gè)環(huán)境中的代碼配置結(jié)構(gòu),編譯器會(huì)自動(dòng)根據(jù)環(huán)境進(jìn)入到指定的代碼進(jìn)行編譯和運(yùn)行。非此環(huán)境下的代碼我們想進(jìn)入去查看是進(jìn)不去,這就是編譯器的智能體現(xiàn)。

二、cache_t的SEL本丟查看

我們都知道 對(duì)象調(diào)用方法都是通過(guò)編譯器進(jìn)行方法查找。而編譯器會(huì)經(jīng)常查找的方法進(jìn)行緩存,下次進(jìn)行方法查找的時(shí)候進(jìn)行先進(jìn)入緩存中查找,這樣會(huì)大大節(jié)省時(shí)間,從而達(dá)到快速的作用,cache_t就是為此而生的。正好解決這個(gè)查找問(wèn)題。
接下來(lái)我們分兩種不同的環(huán)境進(jìn)行調(diào)試和學(xué)習(xí)cache_t的內(nèi)部_buckets,也就是selimp,在iOS開(kāi)發(fā)過(guò)程中,

  • 1 源碼環(huán)境下指令查看
  • 2 脫離源碼進(jìn)行代碼答應(yīng)

1,源碼環(huán)境下指令查看

首先我們創(chuàng)建一個(gè)類LGPerson集成自NSObject 如下

@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *lgName;
@property (nonatomic, strong) NSString *nickName;

- (void)sayHello;

- (void)sayCode;

- (void)sayMaster;

- (void)sayNB;

+ (void)sayHappy;

@end

在接下來(lái)進(jìn)行相關(guān)的指令調(diào)試步驟查看相應(yīng)的cache_t

  • 1 創(chuàng)建對(duì)象,獲取對(duì)象的類,將斷點(diǎn)斷住相應(yīng)的位置


    斷點(diǎn)調(diào)試.png
  • 2 在控制臺(tái)進(jìn)行打印類信息

p/x pClass

結(jié)果是:

(Class) $0 = 0x00000001000022a8 LGPerson

  • 3 進(jìn)行偏移 我們知道cache_t和類地址相差16位,正好是0x10 所以cache_t是0x00000001000022b8

  • 4 打印cache_t指針信息;

p (cache_t *)0x00000001000022b8

結(jié)果是

(cache_t *) $1 = 0x00000001000022b8

  • 5 取出相關(guān)cache_t的內(nèi)容;

p *$1

結(jié)果是

(cache_t) $2 = {
_buckets = {
std::__1::atomic<bucket_t *> = 0x000000010032e430 {
_sel = {
std::__1::atomic<objc_selector *> = (null)
}
_imp = {
std::__1::atomic<unsigned long> = 0
}
}
}
_mask = {
std::__1::atomic<unsigned int> = 0
}
_flags = 32804
_occupied = 0
}

  • 6 我們知道類的層級(jí)結(jié)構(gòu)后,知道_buckets里邊存儲(chǔ)的類的selimp,從我們打印的結(jié)果知道,此處的_occupied = 0;也就是第一個(gè)斷點(diǎn)的位置還沒(méi)開(kāi)始存儲(chǔ) sel ,不信我們繼續(xù);

p $2.buckets()

結(jié)果是:

(bucket_t *) $3 = 0x000000010032e430

  • 7 取出buckets_t中的內(nèi)容

p *$3

結(jié)果是

(bucket_t) $4 = {
_sel = {std::__1::atomic<objc_selector *> = (null}
_imp = {
std::__1::atomic<unsigned long> = 0
}
}

  • 8 接下來(lái)我們過(guò)掉一個(gè)斷點(diǎn),執(zhí)行第一個(gè)方法。再次打印結(jié)果;


    第二個(gè)斷點(diǎn)調(diào)試.png
  • 9 再次打印 cache_t中的內(nèi)容

p *$1

結(jié)果是

(cache_t) $5 = {
_buckets = {
std::__1::atomic<bucket_t *> = 0x0000000100661c50 {
_sel = {
std::__1::atomic<objc_selector *> = ""
}
_imp = {
std::__1::atomic<unsigned long> = 10584
}
}
}
_mask = {
std::__1::atomic<unsigned int> = 7
}
_flags = 32804
_occupied = 1
}

  • 10 我們此時(shí)看到 _occupied = 1 也就是緩存中存在了我們調(diào)用的方法了:[p sayHello]已經(jīng)完美執(zhí)行了,接下來(lái)我們?cè)俅悟?yàn)證;

p $5.buckets()

結(jié)構(gòu)是

(bucket_t *) $6 = 0x0000000100661c50

  • 11 取出bucket_t的內(nèi)容;

p *$6

結(jié)果是

(bucket_t) $7 = {
_sel = {
std::__1::atomic<objc_selector *> = ""
}
_imp = {
std::__1::atomic<unsigned long> = 10584
}
}

  • 12 取出sel

p $7.sel()

結(jié)果是

(SEL) $8 = "sayHello"

  • 13 取出imp

p $7.imp(pClass)

結(jié)果是

(IMP) $9 = 0x0000000100000bf0 (KCObjc`-[LGPerson sayHello])

同理過(guò)掉第二個(gè)斷點(diǎn)進(jìn)入第三個(gè),也可以進(jìn)行相關(guān)的打印,_occupied = 2 用相關(guān)的指令也能打印相關(guān)內(nèi)容;

2、脫離源碼進(jìn)行代碼答應(yīng)

從以文章開(kāi)頭介紹,cache_t,依靠系統(tǒng)的幾部分內(nèi)容

  • 1 _buckets
typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits

struct lg_bucket_t {
    SEL _sel;
    IMP _imp;
};
  • 2 cache_t
struct lg_cache_t {
    struct lg_bucket_t * _buckets;
    mask_t _mask;
    uint16_t _flags;
    uint16_t _occupied;
};
  • 3 class_data_bits_t
struct lg_class_data_bits_t {
    uintptr_t bits;
};
  • 4 objc_class
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
};
  • 5 接下來(lái)創(chuàng)建類,并調(diào)用相關(guān)的兩個(gè)方法
        LGPerson *p  = [LGPerson alloc];
        Class pClass = [LGPerson class];  // objc_clas
        [p say1];
        [p say2];
//        [p say3];
//        [p say4];
  • 6 配置打印結(jié)果代碼
      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);
        }
  • 7 打印結(jié)果是


    兩個(gè)方法的打印結(jié)果.png

我們能看到 _occupied = 2_mask = 3 以及相關(guān)的方法對(duì)應(yīng)的實(shí)現(xiàn) 也就是 selimp;

  • 8 我們把第4步的ISA 注釋掉,打印的結(jié)果卻是


    注釋掉類的ISA打印結(jié)果.png

我們能看到 _occupied = 0_mask = 5380272 未知情況

  • 9 我們?cè)俅未蛴?個(gè)方法查看打印結(jié)果、[p say1][p say2]、[p say3][p say4]
    4個(gè)方法答應(yīng)的結(jié)果圖.png

我們能看到 _occupied = 2_mask = 7,明確的知道m(xù)ask 已經(jīng)從原來(lái)的 3 變化到7,那么為什么打印的方法還是只有兩個(gè)呢,這就是我們接下來(lái)研究的mask的機(jī)制和擴(kuò)容的奧秘了。

三、cache_t 的buckets 和mask的機(jī)制探索

從上邊的問(wèn)題 mask 已經(jīng)從原來(lái)的 3 變化到7,就是存在一個(gè)mask 的調(diào)整,那么mask 最大能到多少呢?

 explicit_atomic<uintptr_t> _maskAndBuckets;
    mask_t _mask_unused;

    static constexpr uintptr_t maskShift = 48;
    static constexpr uintptr_t maskZeroBits = 4;
    static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
    static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;
  • maskShift = 48
  • maxMask = (1 << 16 ) - 1 = 2^16 -1
  • bucketsMask = (1<<44) - 1 = 2^44 -1


    mask 變化前后圖.png

四、cache_t下的sel存儲(chǔ)機(jī)制

我們從objc781 開(kāi)源代碼能清楚的知道cache_t 的過(guò)程是

  • 1 cache_fill
  • 2 cache_t::insert
  • 3 cache_create
  • 4 bcopy
  • 5 flush_caches
  • 6 cache_flush
  • 7 cache_collect_free

1 cache_fill

我們知道創(chuàng)建一個(gè)方法需要先走cache_fill,代碼定義如下:

void cache_fill(Class cls, SEL sel, IMP imp, id receiver)
{
    runtimeLock.assertLocked();
    if (cls->isInitialized()) {
        cache_t *cache = getCache(cls);
        cache->insert(cls, sel, imp, receiver);
    }

}

2 cache_t::insert (最核心)

代碼定義如下

ALWAYS_INLINE
void cache_t::insert(Class cls, SEL sel, IMP imp, id receiver)
{


    // part1 計(jì)算相關(guān)的occupied
    mask_t newOccupied = occupied() + 1;
    unsigned oldCapacity = capacity(), capacity = oldCapacity;
   // part2 判斷如果是創(chuàng)建 進(jìn)行初始化
    if (slowpath(isConstantEmptyCache())) {
        // Cache is read-only. Replace it.
        if (!capacity) capacity = INIT_CACHE_SIZE;
        reallocate(oldCapacity, capacity, /* freeOld */false);
    }
//part3 判斷是否需要擴(kuò)容
    else if (fastpath(newOccupied + CACHE_END_MARKER <= capacity / 4 * 3)) { // 4  3 + 1 bucket cache_t
        // Cache is less than 3/4 full. Use it as-is.
    }
//part4 擴(kuò)容操作;
    else {
        capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;  // 擴(kuò)容兩倍 4
        if (capacity > MAX_CACHE_SIZE) {
            capacity = MAX_CACHE_SIZE;
        }
        reallocate(oldCapacity, capacity, true);  // 內(nèi)存 庫(kù)容完畢
    }

    bucket_t *b = buckets();
    mask_t m = capacity - 1;
    mask_t begin = cache_hash(sel, m);
    mask_t i = begin;

    //part5;進(jìn)行相關(guān)的方法存儲(chǔ)
    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);
}

首先將代碼的定義分配為5部分;代碼里已經(jīng)注釋的很清楚了

part1 計(jì)算相關(guān)的新的newOccupied
    mask_t newOccupied = occupied() + 1;
part2.判讀第一次進(jìn)行初始化操作
  • 1 計(jì)算新值
if (!capacity) capacity = INIT_CACHE_SIZE;
        reallocate(oldCapacity, capacity, /* freeOld */false);
  • 2 INIT_CACHE_SIZE 的定義如下

INIT_CACHE_SIZE_LOG2 = 2,
INIT_CACHE_SIZE = (1 << INIT_CACHE_SIZE_LOG2),

也就是1 << 2,就是4.也就是說(shuō)默認(rèn)進(jìn)來(lái)分配4的內(nèi)存空間;

  • 3 再進(jìn)行setBucketsAndMask
 setBucketsAndMask(newBuckets, newCapacity - 1);

具體函數(shù)就是


void cache_t::setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask)
{

#ifdef __arm__
    mega_barrier();
    _buckets.store(newBuckets, memory_order::memory_order_relaxed);
    mega_barrier();
    _mask.store(newMask, memory_order::memory_order_relaxed);
    _occupied = 0;
#elif __x86_64__ || i386
    _buckets.store(newBuckets, memory_order::memory_order_release);
    _mask.store(newMask, memory_order::memory_order_release);
    _occupied = 0;
#else

也即是向內(nèi)存中存儲(chǔ)相關(guān)的sel操作;再次把_occupied = 0 ;也就是不占用任何空間,也就是初始化的的操作,只是一個(gè)空殼子,不存在實(shí)質(zhì)性的操作;

  • 4 如果舊的值存在,則全部釋放cache_collect_free
static void cache_collect_free(bucket_t *data, mask_t capacity)
{
    if (PrintCaches) recordDeadCache(capacity);
    _garbage_make_room ();
    garbage_byte_size += cache_t::bytesForCapacity(capacity);
    garbage_refs[garbage_count++] = data;
    cache_collect(false);
}
part3 如果新的值小于或等于原來(lái)的3/4,不做任何處理;
if (fastpath(newOccupied + CACHE_END_MARKER <= capacity / 4 * 3)) { // 4  3 + 1 bucket cache_t
        // Cache is less than 3/4 full. Use it as-is.
    }
part4.超過(guò)原來(lái)的3/4,進(jìn)行內(nèi)存擴(kuò)容;
     capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;  // 擴(kuò)容兩倍 4
        if (capacity > MAX_CACHE_SIZE) {
            capacity = MAX_CACHE_SIZE;
        }
        reallocate(oldCapacity, capacity, true);  // 內(nèi)存 庫(kù)容完畢

也就是將原來(lái)的內(nèi)存擴(kuò)容到當(dāng)前的2倍;然后始終保持mask_t m = capacity - 1;這也就是為什么之前我們打印的mask從3變化到7的原因;

因?yàn)槲覀冊(cè)瓉?lái)的內(nèi)存大小是4,因?yàn)橥瑫r(shí)執(zhí)行了4個(gè)方法,存儲(chǔ)已經(jīng)超過(guò)了原來(lái)的3/4,所以擴(kuò)容到8.而根據(jù)mask_t m = capacity - 1;,所以原來(lái)的是 mask = 4- 1 = 3, 而新的mask = 8- 1 = 7

part5方法的存儲(chǔ)機(jī)制
  • 1 在iOS開(kāi)發(fā)中我們很多數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)都是以快速為主,例如字典,內(nèi)存映射等,其目的都是為了快速的查找想要的到的內(nèi)容。同理。cache_t 也不例外,其存儲(chǔ)的代碼如下
 mask_t begin = cache_hash(sel, m);
   mask_t i = begin;
  • 2 查看· cache_hash的內(nèi)部結(jié)構(gòu)
static inline mask_t cache_hash(SEL sel, mask_t mask) 
{
    return (mask_t)(uintptr_t)sel & mask;
}
  • 3 通過(guò)上面的cache_hash 和mask 進(jìn)行相關(guān)的操作。我們都知道任何方法在內(nèi)存中都存在一個(gè)方法編號(hào),用這個(gè)方法編號(hào)進(jìn)行與操作,就能準(zhǔn)確的得到這個(gè)方法在cache中的索引;

  • 4 如果得到的索引存在沖突,則繼續(xù)處理hash 沖突;

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));

通過(guò)這種方法,那么相關(guān)的方法在類cache_t中就能有并且存儲(chǔ)是一個(gè)唯一的索引,通過(guò)查找方法我們就能快速的查找到;

五、總結(jié)

通過(guò)將近5個(gè)小時(shí)的整理和斷點(diǎn)調(diào)試,終于寫完這次的內(nèi)容,雖然內(nèi)容過(guò)于簡(jiǎn)單,但是還是自己實(shí)現(xiàn)了一遍流程,也算是一種收獲吧,希望以后再接再厲。繼續(xù)努力;如果大神們有什么好的建議請(qǐng)不吝賜教。謝謝。

?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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