iOS底層原理之cache_t分析

咱們在iOS底層原理之isa走位與類結構分析 中分析了objc_class結構體中的bits屬性,今天咱們分析cache_t屬性。數組

什麼是cache_t

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

#if __LP64__
    bool getBit(uint16_t flags) const {
        return _flags & flags;
    }
    void setBit(uint16_t set) {
        __c11_atomic_fetch_or((_Atomic(uint16_t) *)&_flags, set, __ATOMIC_RELAXED);
    }
    void clearBit(uint16_t clear) {
        __c11_atomic_fetch_and((_Atomic(uint16_t) *)&_flags, ~clear, __ATOMIC_RELAXED);
    }
#endif

#if FAST_CACHE_ALLOC_MASK
    bool hasFastInstanceSize(size_t extra) const
    {
        if (__builtin_constant_p(extra) && extra == 0) {
            return _flags & FAST_CACHE_ALLOC_MASK16;
        }
        return _flags & FAST_CACHE_ALLOC_MASK;
    }

    size_t fastInstanceSize(size_t extra) const
    {
        ASSERT(hasFastInstanceSize(extra));

        if (__builtin_constant_p(extra) && extra == 0) {
            return _flags & FAST_CACHE_ALLOC_MASK16;
        } else {
            size_t size = _flags & FAST_CACHE_ALLOC_MASK;
            // remove the FAST_CACHE_ALLOC_DELTA16 that was added
            // by setFastInstanceSize
            return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
        }
    }

    void setFastInstanceSize(size_t newSize)
    {
        // Set during realization or construction only. No locking needed.
        uint16_t newBits = _flags & ~FAST_CACHE_ALLOC_MASK;
        uint16_t sizeBits;

        // Adding FAST_CACHE_ALLOC_DELTA16 allows for FAST_CACHE_ALLOC_MASK16
        // to yield the proper 16byte aligned allocation size with a single mask
        sizeBits = word_align(newSize) + FAST_CACHE_ALLOC_DELTA16;
        sizeBits &= FAST_CACHE_ALLOC_MASK;
        if (newSize <= sizeBits) {
            newBits |= sizeBits;
        }
        _flags = newBits;
    }
#else
    bool hasFastInstanceSize(size_t extra) const {
        return false;
    }
    size_t fastInstanceSize(size_t extra) const {
        abort();
    }
    void setFastInstanceSize(size_t extra) {
        // nothing
    }
#endif

    static size_t bytesForCapacity(uint32_t cap);
    static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);

    void reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld);
    void insert(Class cls, SEL sel, IMP imp, id receiver);

    static void bad_cache(id receiver, SEL sel, Class isa) __attribute__((noreturn, cold));
};
複製代碼

從上述源碼中咱們能夠看出,cache_t中存儲了函數方法的集合以及聲明的變量等。緩存

_buckets、_mask、_occupied

  • _buckets:buket_t類型的結構體數組,存儲方法指針地址_IMP以及方法編號_SEL
  • _mask:mask_t m = capacity - 1,能夠理解爲cache_t的緩存容量大小,因爲(capacity = MAX_CACHE_SIZE;)MAX_CACHE_SIZE是2整數次冪
  • _occupied:記錄緩存方法的個數

lldbcache_t代碼跟蹤

咱們在LGPerson類中聲明一下方法,並實現markdown

@interface LGPerson : NSObject

- (void)sayHello;

- (void)sayCode;

- (void)sayMaster;

- (void)sayNB;

+ (void)sayHappy;

@end

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

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

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

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

+ (void)sayHappy{
    NSLog(@"LGPerson say : %s",__func__);
}
@end
複製代碼

在main()函數中代碼實現app

LGPerson *p  = [LGPerson alloc];
Class pClass = [LGPerson class];
[p sayHello];
[p sayCode];
[p sayMaster];
複製代碼
  • 先查看未調用任何實例方法時候狀況

此時 咱們能夠看到cache尚未存儲方法函數

  • 再看調用一個實例方法後狀況

此時_buckets、_mask、_occupied均發生了變化oop

  • 調用兩個實例方法後狀況此時_mask = 3_occupied = 2
  • 當調用三個實例方法後狀況此時_mask = 7_occupied = 1。爲何_mask變換這麼大以及_occupied爲何不是3?想要解決這個問題咱們要看它們是怎麼實現的。

cache_t 底層實現

探究cache_t初始化

咱們從cache_t結構體中發現上述方法,咱們在源碼中搜索incrementOccupied中發現了初始化cache_t,發現咱們在方法中發現了調用的地方,咱們探究下該方法咱們先看須要注意的兩處代碼fetch

  • INIT_CACHE_SIZE
enum {
    INIT_CACHE_SIZE_LOG2 = 2,
    INIT_CACHE_SIZE      = (1 << INIT_CACHE_SIZE_LOG2),
    MAX_CACHE_SIZE_LOG2  = 16,
    MAX_CACHE_SIZE       = (1 << MAX_CACHE_SIZE_LOG2),
};
複製代碼

經過INIT_CACHE_SIZE,咱們知道每次開闢的大小是2的冪,第一次是4,因此當咱們的調用一個或兩個實例方法的時候_mask = 3ui

  • reallocate方法中
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);
    }
}
複製代碼

咱們發現了setBucketsAndMask方法,setBucketsAndMask代碼爲this

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
}
複製代碼

setBucketsAndMask方法中將_occupied = 0初始化爲0了,所以咱們獲得每次擴容後,_occupied都從0開始再次計數,估調用三個實例方法後_occupied = 1atom

cache_t總結

  • cache經過void cache_t::insert(Class cls, SEL sel, IMP imp, id receiver)方法緩存調用的方法
  • ① 若是第一次添加,則經過reallocate方法開闢空間,在reallocate方法中通setBucketsAndMask方法將_occupied初始化爲0,開闢大小爲4,由於_mask = capacity - 1,其中capacity即爲開闢的大小,因此 _mask = 3;
  • ②若是insert的方法個數小於等於已經開闢個數的3/4則不進行處理
  • ③若是大於3/4,則進行擴容處理,此時經過reallocate擴容,每次擴容按照原先的兩倍來擴容,將_occupied初始化爲0,

cache_t原理圖

相關文章
相關標籤/搜索