之前文章已经看过了bits的内容,对property、method、ivar进行了基础的了解,现在来进行cache的基本分析。
之前用lldb对类的结构进行分析,已经了解了如何获取到bits,同样进行内存平移可以查看cache的内容。
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;
};
......
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));
......
void insert(SEL sel, IMP imp, id receiver);
void copyCacheNolock(objc_imp_cache_entry *buffer, int len);
void destroy();
void eraseNolock(const char *func);
}
缓存无非是对于类中的方法属性等进行缓存,通过其中的的方法看大部分都是对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
......
终于我们看到了我们要找的SEL和IMP,接下来验证方法的缓存
获取内容查看结构体中提供的方法struct bucket_t *buckets() const;
但是当前的内容中并没有看到我们预期的结果,因为当前我们还没有进行方法的调用,我们对方法进行一次调用,然后再看当前的内容
调用bucket_t的sel方法
同样的方式查看IMP
当方法被调用后,才会缓存到cache中,为了提高方法查找效率。
方法缓存的过程
插入方法
void insert(SEL sel, IMP imp, id receiver);
void cache_t::insert(SEL sel, IMP imp, id receiver)
{
runtimeLock.assertLocked();
......
// Use the cache as-is if until we exceed our expected fill ratio.
mask_t newOccupied = occupied() + 1;
unsigned oldCapacity = capacity(), capacity = oldCapacity;
if (slowpath(isConstantEmptyCache())) {
// 第一次cache为空的时候初始化
// INIT_CACHE_SIZE = 1 << 2
// Cache is read-only. Replace it.
if (!capacity) capacity = INIT_CACHE_SIZE;
// 调用setBucketsAndMask(newBuckets, newCapacity - 1)给_bucketsAndMaybeMask复制,_occupoed = 0
reallocate(oldCapacity, capacity, /* freeOld */false);
}
else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) {
/*
// 判断扩容条件,如果大于75%进行扩容,如果不大于,进行正常操作
static inline mask_t cache_fill_ratio(mask_t capacity) {
return capacity * 3 / 4;
}
*/
// Cache is less than 3/4 or 7/8 full. Use it as-is.
}
#if CACHE_ALLOW_FULL_UTILIZATION
else if (capacity <= FULL_UTILIZATION_CACHE_SIZE && newOccupied + CACHE_END_MARKER <= capacity) {
// Allow 100% cache utilization for small buckets. Use it as-is.
}
#endif
else {
// 如果超出75%的容量,进行两倍扩容
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
if (capacity > MAX_CACHE_SIZE) {
capacity = MAX_CACHE_SIZE;
}
// 当调用四个方法时,会发现只剩下最后调用的一个方法,原理就在这
// 当调用第四个方法时需要扩容,当前方法传入参数true
// 会对旧的内存进行清空操作 collect_free(oldBuckets, oldCapacity);
/*
*/
reallocate(oldCapacity, capacity, true);
}
bucket_t *b = buckets(); // 获取到buckets数组
mask_t m = capacity - 1;
mask_t begin = cache_hash(sel, m); // 通过对sel和m进行哈希确定应该存储的位置
mask_t i = begin;
// 通过循环查找找到第一个没有用到位置进行插入
// Scan for the first unused slot and insert there.
// There is guaranteed to be an empty slot.
do {
if (fastpath(b[i].sel() == 0)) {
// 如果没有缓存当前方法,_occupied ++,并将方法进行缓存
incrementOccupied();
b[i].set<Atomic, Encoded>(b, 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)); // 防止哈希冲突进行再次哈希
bad_cache(receiver, (SEL)sel);
#endif // !DEBUG_TASK_THREADS
}
调试过程中比较重要的但我们并没有用到的还有_bucketsAndMaybeMask,实际上缓存方法的重要信息都存放到_bucketsAndMaybeMask指向的内容,其中的Value对应的就是buckets,在buckets的实现中, bucketsMask是~0ul所以就是取地址
struct bucket_t *cache_t::buckets() const
{
uintptr_t addr = _bucketsAndMaybeMask.load(memory_order_relaxed);
return (bucket_t *)(addr & bucketsMask);
}