iOS底層之cache_t探究

我們?cè)?a href="http://www.reibang.com/p/833e70929642" target="_blank">iOS底層之類的結(jié)構(gòu)分析分析了類的內(nèi)部結(jié)構(gòu)挣磨,而類的C/C++底層實(shí)際是objc_class結(jié)構(gòu)體榕暇,其中包含了4個(gè)成員。

類的底層結(jié)構(gòu)

那么這一節(jié)我們來(lái)探究cache_t——類的方法緩存的真面目鸯绿,存儲(chǔ)了什么坐搔,怎么存儲(chǔ)的杭煎。

cache_t的結(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;
    mask_t _mask_unused;
}
#endif
    
#if __LP64__
    uint16_t _flags;
#endif
    uint16_t _occupied;
  // ......
//省略了其他靜態(tài)成員和方法

這里的系統(tǒng)架構(gòu)的判斷條件宏定義是:

#define CACHE_MASK_STORAGE_OUTLINED 1
#define CACHE_MASK_STORAGE_HIGH_16 2
#define CACHE_MASK_STORAGE_LOW_4 3

#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
#endif

可以看到
CACHE_MASK_STORAGE_HIGH_16——真機(jī)64位架構(gòu)
CACHE_MASK_STORAGE_LOW_4——真機(jī)非64位架構(gòu)
CACHE_MASK_STORAGE_OUTLINED——非真機(jī)即macOS環(huán)境

macOS環(huán)境和真機(jī)64位環(huán)境下纽哥,它們的區(qū)別是:

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

cache_t的成員主要包括了:

  1. macOS環(huán)境下:
    _buckets:桶,包含了sel方法編號(hào)代箭、imp函數(shù)指針地址墩划。原子安全的。
    _mask:掩碼嗡综。原子安全的乙帮。
  2. 64位真機(jī)環(huán)境下 :
    _maskAndBuckets:將_mask_buckets放在一起。也就是存儲(chǔ)_mask & _buckets的結(jié)果极景。原子安全的察净。
    _mask_unused:未使用的掩碼,全局搜索后發(fā)現(xiàn)戴陡,這個(gè)成員并沒有被使用塞绿,猜測(cè)可能是沒開源出來(lái)或者是這部分還未寫完。

公共成員:
_flags:位置標(biāo)識(shí)恤批。
_occupied:已占用的异吻。

1.成員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
}

可以看到存儲(chǔ)了_imp_sel,它們都是原子安全的喜庞,區(qū)別是诀浪,它們的順序不一樣。在arm64位環(huán)境下延都,imp放在前面對(duì)arm64e架構(gòu)的ptrauth更好雷猪,而其他環(huán)境下,sel放在前面對(duì)armv7*晰房、i386求摇、 x86_64架構(gòu)更優(yōu)。

那么我們知道了bucket_t的結(jié)構(gòu)殊者,就可以通過項(xiàng)目去查看下我們實(shí)例的對(duì)象的方法編號(hào)和實(shí)現(xiàn)了与境。

定義一個(gè)類,包括屬性和實(shí)例方法猖吴、類方法摔刁。

@interface BKPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSString *nickName;

- (void)sayHello;

- (void)sayWorld;

- (void)sayMaster;

- (void)sayNB;

+ (void)sayHappy;

@end

@implementation BKPerson
- (void)sayHello{
    NSLog(@"BKPerson say : %s",__func__);
}

- (void)sayWorld{
    NSLog(@"BKPerson say : %s",__func__);
}

- (void)sayMaster{
    NSLog(@"BKPerson say : %s",__func__);
}

- (void)sayNB{
    NSLog(@"BKPerson say : %s",__func__);
}

+ (void)sayHappy{
    NSLog(@"BKPerson say : %s",__func__);
}

在main.m文件中

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        BKPerson *p  = [BKPerson alloc];
        Class pClass = [BKPerson class];
        [p sayHello];
        [p sayWorld];
        [p sayMaster];


        NSLog(@"%@",pClass);
    }
    return 0;
}

[p sayHello];設(shè)置一個(gè)斷點(diǎn),運(yùn)行海蔽,并打印調(diào)試BKPerson類的cache_t信息:

這一步通過打印類的內(nèi)存首地址共屈,偏移16個(gè)字節(jié)(isasuperclass成員各占了8字節(jié))后得到的內(nèi)存位置就是cache_t的信息绑谣。打印可以看到,這時(shí)候只初始化了類的實(shí)例對(duì)象拗引,還沒有執(zhí)行實(shí)例方法借宵。_buckets里的_sel=null,_imp=0寺擂,而且_mask=0暇务,_occupied=0

我們將斷點(diǎn)跳到下一行代碼怔软。再打印cache_t


可以看到這時(shí)候的cache_t里面是:_buckets里的_sel=""垦细,_imp=11928,而且_mask=3挡逼,_occupied=1括改。而這個(gè)變數(shù)是因?yàn)槲覀儓?zhí)行了sayHello這個(gè)方法。

難道真的是因?yàn)閳?zhí)行了sayHello嗎家坎,我不信嘱能。
為了驗(yàn)證這一點(diǎn),那么可以打印下_buckets的信息:

??這里要注意的是:如果直接使用打印的信息里的成員去打印虱疏,像圖上那樣的惹骂,是會(huì)取不到的。這時(shí)候我們可以查看cache_t的定義做瞪,看看有沒有相關(guān)的方法对粪。

struct cache_t {
    struct bucket_t *buckets();
    mask_t mask();
    mask_t occupied();
}

可以找到以上方法,而如果要打印selimp装蓬,則需要查看bucket_t的定義:


struct bucket_t {
    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
    }
}

這時(shí)再通過以上方法打又谩:


可以百分百確定,由于執(zhí)行到了sayHello方法牍帚,所以cache_t存儲(chǔ)的信息發(fā)生了改變儡遮。

進(jìn)一步驗(yàn)證,我們把斷點(diǎn)跳到下一行暗赶。執(zhí)行了sayWorld方法鄙币。

可以看到這時(shí)的_occupied=2,但是我們繼續(xù)用之前的方式打印selimp蹂随,卻還是只能打印出sayHello爱榔,而sayWorld沒看到,但是通過成員名_buckets可以確定這是一個(gè)集合類型糙及。

那么問題是,如何打印出_buckets這個(gè)集合里的所有元素筛欢?

這里有兩種方式:
第一種:使用數(shù)組下標(biāo)的方式訪問元素


既然我們知道buckets是個(gè)數(shù)組浸锨,那么可以用下標(biāo)方式唇聘,取出第0個(gè)、第1個(gè)元素柱搜,而打印第1個(gè)元素迟郎,可以得到sayWorld的信息。

第二種:使用地址偏移聪蘸,訪問集合元素


我們知道宪肖,對(duì)于一個(gè)數(shù)組,我們可以將數(shù)組指針+下標(biāo)數(shù)健爬,之后對(duì)其*取值控乾,得出元素。比如娜遵,這里sayWorld是第二個(gè)元素蜕衡,則對(duì)buckets數(shù)組指針,進(jìn)行+1设拟,之后用*整體取值慨仿,可以得出sayWorld元素。

可以發(fā)現(xiàn)纳胧,我們現(xiàn)在是在源碼環(huán)境镰吆,而每次打印值都需要$取變量,很麻煩跑慕。那我們能在日常開發(fā)環(huán)境中進(jìn)行調(diào)試嗎万皿?
答案是肯定的。
脫離源碼環(huán)境相赁,我們可以模擬底層的類型結(jié)構(gòu)相寇,一樣可以調(diào)試。

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

struct my_bucket_t {
    SEL _sel;
    IMP _imp;
};

struct my_cache_t {
    struct my_bucket_t * _buckets;
    mask_t _mask;
    uint16_t _flags;
    uint16_t _occupied;
};

struct my_class_data_bits_t {
    uintptr_t bits;
};

struct my_objc_class {
    Class ISA;
    Class superclass;
    struct my_cache_t cache;             // formerly cache pointer and vtable
    struct my_class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
};

我們只需要取我們需要的成員钮科,繼承的關(guān)系也可以不要了唤衫。而這里需要注意的是:由于objc_class是繼承于objc_object,也就繼承了isa成員绵脯,所以我們?nèi)サ袅死^承的關(guān)系后佳励,就要加上isa成員。否則蛆挫,在打印類的cache_t的相關(guān)信息時(shí)赃承,就會(huì)打印不到正確的值。

之后就可以打印類的方法selimp了悴侵。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        BKPerson *p  = [BKPerson alloc];
        Class pClass = [BKPerson class];  // objc_clas
        [p eat1];
        [p eat2];
        [p eat3];
        [p eat4];
        
        struct my_objc_class *my_pClass = (__bridge struct my_objc_class *)(pClass);
        NSLog(@"%hu - %u",my_pClass->cache._occupied,my_pClass->cache._mask);
        for (mask_t i = 0; i<my_pClass->cache._mask; i++) {
            // 打印獲取的 bucket
            struct my_bucket_t bucket = my_pClass->cache._buckets[I];
            NSLog(@"%@ - %p",NSStringFromSelector(bucket._sel),bucket._imp);
        }

        NSLog(@"Hello, World!");
    }
    return 0;
}

但是當(dāng)我們只執(zhí)行eat1eat2瞧剖,和執(zhí)行eat1~eat4,對(duì)比打印信息,可以看到:

可以看到_occupied都是2抓于,而_mask2變成7做粤,而且后一張圖的buckets里打印的eat1eat2不見了,而且eat3eat4的順序捉撮,是錯(cuò)亂的怕品。

提出問題:_occupied_mask是什么,為什么_occupied都是2巾遭,_mask2變成7肉康,為什么有些方法缺失了,為什么順序混亂灼舍?

可以知道吼和,產(chǎn)生變化是由于函數(shù)的調(diào)用,而非成員屬性發(fā)生變化片仿。
那么從源碼查找cache_t纹安,主要包含了以下的函數(shù):

struct cache_t {
    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();
}

點(diǎn)入occupied()

mask_t cache_t::occupied() 
{
    return _occupied;
}

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

可以看到occupied()是一個(gè)get方法,incrementOccupied()是對(duì)_occupied的遞增操作砂豌。這就是重點(diǎn)所在厢岂。

全局搜索incrementOccupied()的調(diào)用,可以看到在insert函數(shù)里調(diào)用了:

ALWAYS_INLINE
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)) { // 4  3 + 1 bucket cache_t
        // Cache is less than 3/4 full. Use it as-is.
    }
    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)存 擴(kuò)容完畢
    }

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

insert分析

mask_t newOccupied = occupied() + 1;:對(duì)occupied()加一阳距。

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)) { // 4  3 + 1 bucket cache_t
        // Cache is less than 3/4 full. Use it as-is.
    }
    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)存 擴(kuò)容完畢
    }

這一步有三個(gè)條件跳轉(zhuǎn)語(yǔ)句:

1. 如果是初始化:

由于初始化的情況比較少塔粒,所以系統(tǒng)使用slowpath修飾條件以加快指令跳轉(zhuǎn),當(dāng)isConstantEmptyCache()緩存為空的時(shí)候筐摘,也就是剛初始化時(shí)卒茬,if (!capacity) capacity = INIT_CACHE_SIZE;,給capacity賦初值為4咖熟。

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

從上面宏知道INIT_CACHE_SIZE的值為4圃酵。
之后執(zhí)行reallocate(oldCapacity, capacity, /* freeOld */false);重新梳理。

ALWAYS_INLINE
void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld)
{
    bucket_t *oldBuckets = buckets();
    bucket_t *newBuckets = allocateBuckets(newCapacity);

    // Cache's old contents are not propagated. 
    // This is thought to save cache memory at the cost of extra cache fills.
    // fixme re-measure this

    ASSERT(newCapacity > 0);
    ASSERT((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1);

    setBucketsAndMask(newBuckets, newCapacity - 1);
    
    if (freeOld) {
        cache_collect_free(oldBuckets, oldCapacity);
    }
}

reallocate這個(gè)函數(shù)里主要做的是:根據(jù)newCapacity開辟buckets內(nèi)存馍管,而setBucketsAndMask函數(shù)定義

void cache_t::setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask)
{
    // objc_msgSend uses mask and buckets with no locks.
    // It is safe for objc_msgSend to see new buckets but old mask.
    // (It will get a cache miss but not overrun the buckets' bounds).
    // It is unsafe for objc_msgSend to see old buckets and new mask.
    // Therefore we write new buckets, wait a lot, then write new mask.
    // objc_msgSend reads mask first, then buckets.

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

做了初始化的工作:根據(jù)_buckets_mask進(jìn)行內(nèi)存初始化工作郭赐,_occupied設(shè)置為0

2. 如果小于capacity3/4時(shí)
fastpath(newOccupied + CACHE_END_MARKER <= capacity / 4 * 3)

#define CACHE_END_MARKER 1

可以看到這個(gè)宏定義為1确沸,也就是fastpath大部分情況下是這個(gè)條件newOccupied+1<= capacity的3/4捌锭,則不執(zhí)行任何語(yǔ)句。

3. 其他情況:也就是newOccupied+1>capacity的3/4
這時(shí)候則需要內(nèi)存擴(kuò)容:
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;,如果capacity等于0罗捎,則給初始值4观谦,否則,擴(kuò)容為原來(lái)的兩倍桨菜。
if (capacity > MAX_CACHE_SIZE) { capacity = MAX_CACHE_SIZE; }

    MAX_CACHE_SIZE_LOG2  = 16,
    MAX_CACHE_SIZE       = (1 << MAX_CACHE_SIZE_LOG2),

防止擴(kuò)容后的capacity超出最大值豁状,最大值為1左移16位捉偏。
之后重新梳理內(nèi)存reallocate(oldCapacity, capacity, true);,要注意這里傳入的第三個(gè)參數(shù)為true替蔬,所以會(huì)執(zhí)行reallocate函數(shù)里的清理舊垃圾的代碼告私。

if (freeOld) {
   cache_collect_free(oldBuckets, oldCapacity);
}

cache_collect_free的定義

static void cache_collect_free(bucket_t *data, mask_t capacity)
{
#if CONFIG_USE_CACHE_LOCK
    cacheUpdateLock.assertLocked();
#else
    runtimeLock.assertLocked();
#endif

    if (PrintCaches) recordDeadCache(capacity);

    _garbage_make_room ();
    garbage_byte_size += cache_t::bytesForCapacity(capacity);
    garbage_refs[garbage_count++] = data;
    cache_collect(false);
}
static void _garbage_make_room(void)
{
    static int first = 1;

    // Create the collection table the first time it is needed
    if (first)
    {
        first = 0;
        garbage_refs = (bucket_t**)
            malloc(INIT_GARBAGE_COUNT * sizeof(void *));
        garbage_max = INIT_GARBAGE_COUNT;
    }

    // Double the table if it is full
    else if (garbage_count == garbage_max)
    {
        garbage_refs = (bucket_t**)
            realloc(garbage_refs, garbage_max * 2 * sizeof(void *));
        garbage_max *= 2;
    }
}
enum {
    INIT_GARBAGE_COUNT = 128
};

作用:上面我們只是給capacity標(biāo)記擴(kuò)容了,那么內(nèi)存也要相應(yīng)擴(kuò)容承桥。

  • _garbage_make_room
    初始時(shí),first=1根悼,將first = 0并且開辟128個(gè)指針類型字節(jié)大小的bucket_t類型指針凶异,垃圾最大值為 128。這時(shí)候的capacity4挤巡。
    如果表已經(jīng)滿了剩彬,也就是垃圾達(dá)到了128。重新開辟兩倍原來(lái)的garbage_max大小的內(nèi)存給bucket_t類型指針矿卑,也就是buckets喉恋。
    否則,則不執(zhí)行任何代碼母廷。
  • garbage_byte_size += cache_t::bytesForCapacity(capacity);
    將舊capacity容量加入整個(gè)垃圾容量里轻黑。
  • garbage_refs[garbage_count++] = data;
    將這一次執(zhí)行的方法緩存先置放在garbage_count下標(biāo)的位置。

總結(jié):前面就是根據(jù)capacity標(biāo)記進(jìn)行方法緩存空間的申請(qǐng)琴昆,不足則需要擴(kuò)容氓鄙。擴(kuò)容時(shí)會(huì)將之前的緩存抹除掉,重新開辟雙倍大小的空間业舍。擴(kuò)容之后可以直接開始存儲(chǔ)了抖拦,不需要算法掃描是否有同樣的方法緩存,更加快速舷暮。

回到insert方法态罪。
下面

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

mask_t m = capacity - 1;根據(jù)這一句,可以知道前面的mask就是capacity - 1下面,如果初始化的時(shí)候复颈,那么mask=3,如果擴(kuò)容后诸狭,就是4*2-1=7券膀。

static inline mask_t cache_hash(SEL sel, mask_t mask) 
{
    return (mask_t)(uintptr_t)sel & mask;
}

傳入selmask的算法計(jì)算哈希表存儲(chǔ)的位置。

運(yùn)行斷點(diǎn)調(diào)試驯遇,可以看到當(dāng)執(zhí)行sayMaster算出的key值為2芹彬,再手動(dòng)傳入sayHellowsayWorldsel之后,得出的key分別為01叉庐。


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

掃描第一個(gè)未使用的插槽舒帮,保證必定會(huì)有一個(gè)可用插槽,因?yàn)樽钚〕叽缡?code>4,而我們前面已經(jīng)修改了為3/4就滿了玩郊。
當(dāng)掃描哈希表時(shí)肢执,大多數(shù)情況下b[i].sel() == 0)成立,也就是空插槽译红,那么就對(duì)occupied1预茄。而

template<Atomicity atomicity, IMPEncoding impEncoding>
void bucket_t::set(SEL newSel, IMP newImp, Class cls)
{
    ASSERT(_sel.load(memory_order::memory_order_relaxed) == 0 ||
           _sel.load(memory_order::memory_order_relaxed) == newSel);

    // objc_msgSend uses sel and imp with no locks.
    // It is safe for objc_msgSend to see new imp but NULL sel
    // (It will get a cache miss but not dispatch to the wrong place.)
    // It is unsafe for objc_msgSend to see old imp and new sel.
    // Therefore we write new imp, wait a lot, then write new sel.
    
    uintptr_t newIMP = (impEncoding == Encoded
                        ? encodeImp(newImp, newSel, cls)
                        : (uintptr_t)newImp);

    if (atomicity == Atomic) {
        _imp.store(newIMP, memory_order::memory_order_relaxed);
        
        if (_sel.load(memory_order::memory_order_relaxed) != newSel) {
#ifdef __arm__
            mega_barrier();
            _sel.store(newSel, memory_order::memory_order_relaxed);
#elif __x86_64__ || __i386__
            _sel.store(newSel, memory_order::memory_order_release);
#else
#error Don't know how to do bucket_t::set on this architecture.
#endif
        }
    } else {
        _imp.store(newIMP, memory_order::memory_order_relaxed);
        _sel.store(newSel, memory_order::memory_order_relaxed);
    }
}

這個(gè)函數(shù)將類的selimp存儲(chǔ)到緩存中。之后return跳出侦厚。
如果插槽不為空耻陕,則判斷插槽里的sel和當(dāng)前要存的sel是否是同一個(gè),如果相同刨沦,說明可能其他線程緩存進(jìn)來(lái)的诗宣,則返回,不存儲(chǔ)想诅。
如果不相同召庞,則執(zhí)行while語(yǔ)句。如果cache表的下一個(gè)插槽的key不等于當(dāng)前begin来破,則執(zhí)行do代碼塊篮灼,如此循環(huán)掃描,直到找到插槽插入讳癌。

static inline mask_t cache_next(mask_t i, mask_t mask) {
    return (i+1) & mask;
}

這是cache_next的算法穿稳。

總結(jié):mask數(shù)值等于capacity - 1,使用cache_hash算法獲得當(dāng)前sel的在哈希表的begin開始掃描位置晌坤,如果begin位置的插槽為空則存儲(chǔ)并返回逢艘,不為空則判斷是否是同一個(gè)sel,相同則返回骤菠,不同則使用cache_next算法得到下一個(gè)掃描位置它改,跟當(dāng)前begin比對(duì),不是同個(gè)插槽商乎,則繼續(xù)循環(huán)上面的查找邏輯央拖。

這樣就可以解釋上面的問題了。
問:_occupied_mask是什么鹉戚,為什么_occupied都是2鲜戒,_mask2變成7,為什么有些方法缺失了抹凳,為什么順序混亂遏餐?

答:_occupied是占用的方法緩存數(shù),_mask是緩存容量的capacity-1赢底,_mask2變成7是因?yàn)?code>capacity初始值為4失都,容量擴(kuò)容算法capacity*2柏蘑,_mask就等于新的capacity-1等于7,而擴(kuò)容會(huì)清理之前的緩存粹庞,導(dǎo)致了方法缺失咳焚,順序混亂是因?yàn)橥ㄟ^哈希算法以selkey存儲(chǔ)導(dǎo)致。

cache_t的原理分析圖如下


cache_t原理分析圖
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末庞溜,一起剝皮案震驚了整個(gè)濱河市革半,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌流码,老刑警劉巖督惰,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異旅掂,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)访娶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門商虐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人崖疤,你說我怎么就攤上這事秘车。” “怎么了劫哼?”我有些...
    開封第一講書人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵叮趴,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我权烧,道長(zhǎng)眯亦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任般码,我火速辦了婚禮妻率,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘板祝。我一直安慰自己宫静,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開白布券时。 她就那樣靜靜地躺著孤里,像睡著了一般。 火紅的嫁衣襯著肌膚如雪橘洞。 梳的紋絲不亂的頭發(fā)上捌袜,一...
    開封第一講書人閱讀 49,792評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音震檩,去河邊找鬼琢蛤。 笑死蜓堕,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的博其。 我是一名探鬼主播套才,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼慕淡!你這毒婦竟也來(lái)了背伴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤峰髓,失蹤者是張志新(化名)和其女友劉穎傻寂,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體携兵,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡疾掰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了徐紧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片静檬。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖并级,靈堂內(nèi)的尸體忽然破棺而出拂檩,到底是詐尸還是另有隱情,我是刑警寧澤嘲碧,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布稻励,位于F島的核電站,受9級(jí)特大地震影響愈涩,放射性物質(zhì)發(fā)生泄漏望抽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一钠署、第九天 我趴在偏房一處隱蔽的房頂上張望糠聪。 院中可真熱鬧,春花似錦谐鼎、人聲如沸舰蟆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)身害。三九已至,卻和暖如春草戈,著一層夾襖步出監(jiān)牢的瞬間塌鸯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工唐片, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留丙猬,地道東北人涨颜。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像茧球,于是被迫代替她去往敵國(guó)和親庭瑰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348