Cache_t
的整体分析
Cache_t
的源码
在objc/objc-runtime-new
源码下查找结构体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;
...//省略,掩码用处
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
explicit_atomic<uintptr_t> _maskAndBuckets;
...//省略
#else
#error Unknown cache mask storage type.
#endif
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
}
那么CACHE_MASK_STORAGE
的判断是什么意义呢?查看CACHE_MASK_STORAGE
宏。
arm64表示真机,LP64表示64位结构
#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//模拟器,MacOS
#endif
lldb
获取cache_t
cache_t 缓存buckets集合
cache_t
下有一个struct bucket_t *buckets();
,这是一个bucket_t
结构体集合的指针,我们可以像访问数组的方式一下,访问buckets
内的内每一个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
...//省略
public:
inline SEL sel() const { return _sel.load(memory_order::memory_order_relaxed); }
inline IMP imp(Class cls) const {
...//省略
}
...//省略
}
Cache_t
的结构图
脱离源码环境调试分析
完整代码
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
struct oc_bucket_t {
SEL _sel;
IMP _imp;
};
struct oc_cache_t {
struct oc_bucket_t * _buckets;
mask_t _mask;
uint16_t _flags;
uint16_t _occupied;
};
struct oc_class_data_bits_t {
uintptr_t bits;
};
struct oc_objc_class {
Class ISA;
Class superclass;
struct oc_cache_t cache; // formerly cache pointer and vtable
struct oc_class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
};
void logClass(Class class){
struct oc_objc_class *lg_pClass = (__bridge struct oc_objc_class *)(class);
NSLog(@"%hu - %u",lg_pClass->cache._occupied,lg_pClass->cache._mask);
for (mask_t i = 0; i<lg_pClass->cache._mask; i++) {
// 打印获取的 bucket
struct oc_bucket_t bucket = lg_pClass->cache._buckets[I];
NSLog(@"%@ - %p",NSStringFromSelector(bucket._sel),bucket._imp);
}
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [Person alloc];
Class pClass = [Person class]; // objc_clas
[p say1];
[p say2];
logClass(pClass);
[p say3];
[p say4];
logClass(pClass);
[p say5];
[p say6];
logClass(pClass);
}
return 0;
}
输出结果
-
调用两个方法时:
- 调用四个方法时:
Cache_t
原理分析
insert
方法分析
- 在
Cache_t
中找到了void incrementOccupied();
函数,对_occupied
进行自增
- 全局搜索
incrementOccupied()
函数,发现只在objc-cache
文件下cache_t
的insert
方法有调用,
-
查看
insert
方法,我们发现进入该方法后,就会对当前的occupied
进行+1
赋值给新的变量newOccupied
,-
如果当前
buckets
为空,则进行重新开辟空间reallocate
,reallocate
中调用setBucketsAndMask
进行初始化,_occupied
等于0,INIT_CACHE_SIZE
为4
.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 }
如果新增的值 小于容量的 ,则什么也不做
否则进行扩容,重新开辟空间,
_occupied
等于0
。最后缓存方法,并对
_occupied
进行自增。
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)) { // Cache is less than 3/4 full. Use it as-is. } else { capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE; if (capacity > MAX_CACHE_SIZE) { capacity = MAX_CACHE_SIZE; } reallocate(oldCapacity, capacity, true); } 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); }
-
Cache_t流程图
附上一个流程图
_occupied
是什么?
_occupied
为分配的内存中已经存储了sel-imp
的的个数
_mask
是什么?
_mask
是指掩码数据
,mask
等于capacity
- 1
为什么buckets
会有丢失?
在reallocate
扩容时,是将原有的内存全部清除了,再重新申请了内存导致
为什么顺序有问题?
哈希算法计算下标,下标是随机的,并不是固定的.