cache_t的内存结构
在类的结构与class_data_bits_t中,我们分析了类的结构,包括isa,superclass,cache,bits。今天我们来看一下cache_t的结构。
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache;
class_data_bits_t bits;
}
查看源码,cache_t的底层结构大致如下,并没有发现特别的东西,那么继续向下看,发现了很多方法与bucket_t有关,查看其结构,诶,发现了sel imp。在这里,也能说明类的cache是用来的缓存方法的,调用过的方法会被缓存在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;
};
}
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
}
LLDB取出cache_t中的方法
定义一个类,调用对象方法,通过源码中的lldb,取出cache_t中缓存的方法。
(lldb) p/x SwwPerson.class
(Class) $0 = 0x0000000100008768 SwwPerson
(lldb) p (cache_t *)0x0000000100008778
(cache_t *) $1 = 0x0000000100008778
(lldb) p *$1
(cache_t) $2 = {
_bucketsAndMaybeMask = {
std::__1::atomic<unsigned long> = {
Value = 4302622544
}
}
= {
= {
_maybeMask = {
std::__1::atomic<unsigned int> = {
Value = 3
}
}
_flags = 32812
_occupied = 1
}
_originalPreoptCache = {
std::__1::atomic<preopt_cache_t *> = {
Value = 0x0001802c00000003
}
}
}
}
(lldb) p $1->buckets()
(bucket_t *) $3 = 0x000000010074cf50
(lldb) p *$3
(bucket_t) $4 = {
_sel = {
std::__1::atomic<objc_selector *> = (null) {
Value = nil
}
}
_imp = {
std::__1::atomic<unsigned long> = {
Value = 0
}
}
}
(lldb) p $1->buckets()[1]
(bucket_t) $5 = {
_sel = {
std::__1::atomic<objc_selector *> = "" {
Value = ""
}
}
_imp = {
std::__1::atomic<unsigned long> = {
Value = 48616
}
}
}
(lldb) p $5.sel
(SEL) $6 = "sayHi"
Fix-it applied, fixed expression was:
$5.sel()
(lldb) p $1->buckets()[2]
cache的插入流程
向cache中插入时,occupied = occupied + 1, 当第一次插入方法时,occupied = 1, 初始化cache,开辟的空间大小为4, capacity = 4。再次插入方法时,occupied = 2, occupied + 1 <= 3/4,所以什么都不做。当再次添加方法时,occupied = 3, cache会进行扩容,capacity*2 = 8,并清除了之前缓存的方法。
为什么会选择3/4做为扩容的大小呢,有一个负载因子的说法,0.75是空间利用率最高的。并且再使用哈希函数计算下标时,也有有效的避免冲突,提高效率。
if (slowpath(isConstantEmptyCache())) {
// Cache is read-only. Replace it.
if (!capacity) capacity = INIT_CACHE_SIZE;
reallocate(oldCapacity, capacity, /* freeOld */false);
}
INIT_CACHE_SIZE 为 1<<2就是4
else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) {
// Cache is less than 3/4 or 7/8 full. Use it as-is.
}
CACHE_END_MARKER = 1
else {
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
if (capacity > MAX_CACHE_SIZE) {
capacity = MAX_CACHE_SIZE;
}
reallocate(oldCapacity, capacity, true);
}