美文网首页
6.iOS底层学习之cache_t探索

6.iOS底层学习之cache_t探索

作者: 牛牛大王奥利给 | 来源:发表于2021-07-17 00:38 被阅读0次

我们通过前面类的学习了解到类的结构中有:isa,superclass,cache,bits。前面研究过isa和superclass的走位图,接下来我们来了解下cache相关。

cache_t

通过源码我们可以看到cache_t的实现如下:

struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask;
#if __LP64__
            uint16_t                   _flags;
#endif
            uint16_t                   _occupied;
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;
    };

#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    // _bucketsAndMaybeMask is a buckets_t pointer
    // _maybeMask is the buckets mask

    static constexpr uintptr_t bucketsMask = ~0ul;
    static_assert(!CONFIG_USE_PREOPT_CACHES, "preoptimized caches not supported");
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
    static constexpr uintptr_t maskShift = 48;
    static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
    static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << maskShift) - 1;
    
    static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS, "Bucket field doesn't have enough bits for arbitrary pointers.");
#if CONFIG_USE_PREOPT_CACHES
    static constexpr uintptr_t preoptBucketsMarker = 1ul;
    static constexpr uintptr_t preoptBucketsMask = bucketsMask & ~preoptBucketsMarker;
#endif
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    // _bucketsAndMaybeMask is a buckets_t pointer in the low 48 bits
    // _maybeMask is unused, the mask is stored in the top 16 bits.

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

#if CONFIG_USE_PREOPT_CACHES
    static constexpr uintptr_t preoptBucketsMarker = 1ul;
#if __has_feature(ptrauth_calls)
    // 63..60: hash_mask_shift
    // 59..55: hash_shift
    // 54.. 1: buckets ptr + auth
    //      0: always 1
    static constexpr uintptr_t preoptBucketsMask = 0x007ffffffffffffe;
    static inline uintptr_t preoptBucketsHashParams(const preopt_cache_t *cache) {
        uintptr_t value = (uintptr_t)cache->shift << 55;
        // masks have 11 bits but can be 0, so we compute
        // the right shift for 0x7fff rather than 0xffff
        return value | ((objc::mask16ShiftBits(cache->mask) - 1) << 60);
    }
#else
    // 63..53: hash_mask
    // 52..48: hash_shift
    // 47.. 1: buckets ptr
    //      0: always 1
    static constexpr uintptr_t preoptBucketsMask = 0x0000fffffffffffe;
    static inline uintptr_t preoptBucketsHashParams(const preopt_cache_t *cache) {
        return (uintptr_t)cache->hash_params << 48;
    }
#endif
#endif // CONFIG_USE_PREOPT_CACHES
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    // _bucketsAndMaybeMask is a buckets_t pointer in the top 28 bits
    // _maybeMask is unused, the mask length is stored in the low 4 bits

    static constexpr uintptr_t maskBits = 4;
    static constexpr uintptr_t maskMask = (1 << maskBits) - 1;
    static constexpr uintptr_t bucketsMask = ~maskMask;
    static_assert(!CONFIG_USE_PREOPT_CACHES, "preoptimized caches not supported");
#else
#error Unknown cache mask storage type.
#endif

    bool isConstantEmptyCache() const;
    bool canBeFreed() const;
    mask_t mask() const;

#if CONFIG_USE_PREOPT_CACHES
    void initializeToPreoptCacheInDisguise(const preopt_cache_t *cache);
    const preopt_cache_t *disguised_preopt_cache() const;
#endif

    void incrementOccupied();
    void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);

    void reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld);
    void collect_free(bucket_t *oldBuckets, mask_t oldCapacity);

    static bucket_t *emptyBuckets();
    static bucket_t *allocateBuckets(mask_t newCapacity);
    static bucket_t *emptyBucketsForCapacity(mask_t capacity, bool allocate = true);
    static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);
    void bad_cache(id receiver, SEL sel) __attribute__((noreturn, cold));

public:
    // The following four fields are public for objcdt's use only.
    // objcdt reaches into fields while the process is suspended
    // hence doesn't care for locks and pesky little details like this
    // and can safely use these.
    unsigned capacity() const;
    struct bucket_t *buckets() const;
    Class cls() const;

#if CONFIG_USE_PREOPT_CACHES
    const preopt_cache_t *preopt_cache() const;
#endif

    mask_t occupied() const;
    void initializeToEmpty();

#if CONFIG_USE_PREOPT_CACHES
    bool isConstantOptimizedCache(bool strict = false, uintptr_t empty_addr = (uintptr_t)&_objc_empty_cache) const;
    bool shouldFlush(SEL sel, IMP imp) const;
    bool isConstantOptimizedCacheWithInlinedSels() const;
    Class preoptFallbackClass() const;
    void maybeConvertToPreoptimized();
    void initializeToEmptyOrPreoptimizedInDisguise();
#else
    inline bool isConstantOptimizedCache(bool strict = false, uintptr_t empty_addr = 0) const { return false; }
    inline bool shouldFlush(SEL sel, IMP imp) const {
        return cache_getImp(cls(), sel) == imp;
    }
    inline bool isConstantOptimizedCacheWithInlinedSels() const { return false; }
    inline void initializeToEmptyOrPreoptimizedInDisguise() { initializeToEmpty(); }
#endif

    void insert(SEL sel, IMP imp, id receiver);
    void copyCacheNolock(objc_imp_cache_entry *buffer, int len);
    void destroy();
    void eraseNolock(const char *func);

    static void init();
    static void collectNolock(bool collectALot);
    static size_t bytesForCapacity(uint32_t cap);

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

这么一大段是不是看着有点想骂人(哈哈哈哈哈),说一下自己面对这个cache的感受,说实话看了一堆一堆的感觉看不懂然后就想放弃,不知道从什么地方入手才好,看下来之后根据自己直觉和经验吧,首先感觉一些基本的声明可以先不用理比如像什么具体了解之后是int类型之类的,但是定义出来比如是int修饰但是很重要的命名比如标志位这种,或者容量这种需要注意一些,因为他可能会关系到容量的增加和减少,也就意味着可能会有相应的增删改查的操作(嗯我是这么理解的),结构体类型要着重去看,一些存取方法可以大致看一眼了解下士那个成员的存取方法,然后还有一些预处理的部分,去查一下发现这些是跟架构有关系的,先来说下这些预处理是什么。来看下宏定义的部分,解释写在注释里:

#if defined(__arm64__) && __LP64__
      #if TARGET_OS_OSX || TARGET_OS_SIMULATOR
              #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS //arm64下的 os系统或者模拟器
      #else
              #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16 //arm64位真机
      #endif
#elif defined(__arm64__) && !__LP64__
      #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4 //arm64位真机  (LP64 其实就是 long integers 和 pointers 是 64 bits,所以非就是不是那个data Models的情况)
#else
      #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED //32位架构的情况或者其他不是上面的情况
#endif

arm64和lp64

所以通过这个宏定义我们可以简化一下代码,只要上面关于macOS的部分,并且根据代源码看到关于buckets的操作有很多包括增删改查这些:

struct cache_t {
    mask_t _mask;
    uint16_t _flags;
    uint16_t _occupied;
public:
    // The following four fields are public for objcdt's use only.
    // objcdt reaches into fields while the process is suspended
    // hence doesn't care for locks and pesky little details like this
    // and can safely use these.
    unsigned capacity() const;
    struct bucket_t *buckets() const;
    Class cls() const;
}

然后我们点击进入到bucket可以查看他的结构如下:

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
//省略若干方法
}

所以我们能得到大致的cache_t的的相关组成为:


641626179633_.pic.jpg

bucket_t

通过上面了解到bucket_t里面主要有sel和imp,除了这两个成员变量的定义,通过源码看到这个结构体中所有的方法基本都是围绕上面这两个成员变量展开的,所以可以推断出这里面主要就记录了sel和imp。然后我找到了sel和imp的定义。

/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;   //用来表示一个方法的选择器

/// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );   //一个指针 指向方法的实现
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
#endif

所以这个bucket_t是和方法有关的,这里面存储的是方法的选择器以及相应的实现指针。

bucket_t::set

void bucket_t::set(bucket_t *base, SEL newSel, IMP newImp, Class cls){
    // 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.
    //这段注释大致的意思是查看旧的imp是不安全的,新的imp是安全的。

    uintptr_t newIMP = (impEncoding == Encoded
                        ? encodeImp(base, newImp, newSel, cls)
                        : (uintptr_t)newImp);

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

上面这段set的操作大概的意思是:Atomic下会根据传进来的sel,imp还有cls新创建一个newimp,把这个新的实现set到memory_order_relaxed这个地方,然后通过sel去读一下memory_order_relaxed的sel是不是等于新传进来的那个sel,如果不等,那么把最新的sel set到相应的memory_order_relaxed或者是memory_order_release(不同的架构)里。非Atomic下会直接更新sel和newimp到相应的位置。这是这个set方法的大致流程。接下来看insert。

cache_t::insert(SEL sel, IMP imp, id receiver)

这是往cache里插入方法时候的过程,我直接在代码里加注释解释。

void cache_t::insert(SEL sel, IMP imp, id receiver){
    runtimeLock.assertLocked();
    // Never cache before +initialize is done
    if (slowpath(!cls()->isInitialized())) { //cls的初始化没完成 那么不会进行insert操作
        return;
    }

    if (isConstantOptimizedCache()) { //关于这个判断 下面直接补充这个函数是一直返回false所以这段不会走,我都怀疑自己看错了哇!
        _objc_fatal("cache_t::insert() called with a preoptimized cache for %s",
                    cls()->nameForLogging());
    }

    inline bool isConstantOptimizedCache(bool strict = false, uintptr_t empty_addr = 0) const { return false; }  //isConstantOptimizedCache这个一直返回false哇

#if DEBUG_TASK_THREADS
    return _collecting_in_critical();
#else
#if CONFIG_USE_CACHE_LOCK
    mutex_locker_t lock(cacheUpdateLock);
#endif

    ASSERT(sel != 0 && cls()->isInitialized());

    // Use the cache as-is if until we exceed our expected fill ratio.
    mask_t newOccupied = occupied() + 1; //有新的方法插入  新的occupied的容量+1
    unsigned oldCapacity = capacity(), capacity = oldCapacity; //获取旧的容量
    if (slowpath(isConstantEmptyCache())) { //判断当前cache是否为空如果是空的 进行初始化
        // Cache is read-only. Replace it.  只读属性 直接替换
        if (!capacity) capacity = INIT_CACHE_SIZE;
        reallocate(oldCapacity, capacity, /* freeOld */false);
    }
    else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity)))  {//如果当前的最新的newOccupied+1小于等于capacity的四分之三 
        // Cache is less than 3/4 or 7/8 full. Use it as-is.
    }else { //否则的话进行扩容 capacity存在的话就是两倍扩容
        capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
        if (capacity > MAX_CACHE_SIZE) {
            capacity = MAX_CACHE_SIZE;
        }
        reallocate(oldCapacity, capacity, true); //这个操作会把旧的释放掉 重新开辟一段两倍的新的。
    }

    bucket_t *b = buckets(); //创建buckets
    mask_t m = capacity - 1; // m是当前长度-1
    mask_t begin = cache_hash(sel, m);  //用传进来的sel和m做与操作得到一个位置,这样与操作得出来的结果不会大于m 也就是不会大于capacity - 1 也就是哈希表的最后一个元素。

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

    mask_t i = begin; //index赋值

    // Scan for the first unused slot and insert there.
    // There is guaranteed to be an empty slot.
    do {
        if (fastpath(b[i].sel() == 0)) { //如果第一次就命中了 直接存储 结束循环
            incrementOccupied();
            b[i].set<Atomic, Encoded>(b, sel, imp, cls());
            return;
        }
        if (b[i].sel() == 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_next继续查找空位置。

  static inline mask_t cache_next(mask_t i, mask_t mask) { //直接往后挪一位继续再哈希 拿到新的位置。
      return (i+1) & mask;
    }
    bad_cache(receiver, (SEL)sel);
#endif // !DEBUG_TASK_THREADS
}

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) { //释放旧值
    collect_free(oldBuckets, oldCapacity);
}

}

这函数就是会把原来旧的释放掉 然后新生成一块空间装新的。

以上就是看了好几天这个地方加上看好几遍视频总结出来的cache,遇见看不懂的我就直接去要么上网搜,要么在源码里面去找相关的注释和定义(我太难了),好多都看不懂,但是越挫越勇,如有问题欢迎讨论!👏🏻👏🏻👏🏻👏🏻

相关文章

网友评论

      本文标题:6.iOS底层学习之cache_t探索

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