在前面的文章里面我們已經(jīng)探索過類的結(jié)構(gòu)《OC中類的結(jié)構(gòu)探索》本篇文章我們重點(diǎn)分析一下cache
cache的結(jié)構(gòu)
我們先看下cache的源碼結(jié)構(gòu),本次繼續(xù)使用objc4-818.2,下面代碼精簡(jiǎn)一下
typedef unsigned long uintptr_t; //8
#if __LP64__
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask;//8
union {
struct {
explicit_atomic<mask_t> _maybeMask; //4
#if __LP64__
uint16_t _flags; //2
#endif
uint16_t _occupied; //2
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;//8(結(jié)構(gòu)體指針)
};
/*
#if defined(__arm64__) && __LP64__
#if TARGET_OS_OSX || TARGET_OS_SIMULATOR
// __arm64__的模擬器
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
#else
//__arm64__的真機(jī)
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16
#endif
#elif defined(__arm64__) && !__LP64__
//32位 真機(jī)
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4
#else
//macOS 模擬器
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED
#endif
****** 中間是不同的架構(gòu)之間的判斷 主要是用來不同類型 mask 和 buckets 的掩碼
*/
public:
void incrementOccupied();
void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
void reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld);
unsigned capacity() const;
struct bucket_t *buckets() const;
Class cls() const;
void insert(SEL sel, IMP imp, id receiver);
//省略。。。
};
我們?cè)谠创a里面發(fā)現(xiàn)有bucket_t還有insert()方法。跟進(jìn)去看一下insert()的實(shí)現(xiàn),也有bucket_t我們猜測(cè)cache里面存儲(chǔ)的內(nèi)容,實(shí)際上就是在bucket_t里面存儲(chǔ)。我們先看下bucket_t的源碼實(shí)現(xiàn)
struct bucket_t {
private:
#if __arm64__
explicit_atomic<uintptr_t> _imp;
explicit_atomic<SEL> _sel;
#else
explicit_atomic<SEL> _sel;
explicit_atomic<uintptr_t> _imp;
#endif
}
這個(gè)一看我們太熟悉了,里面就是sel和imp,我們用lldb動(dòng)態(tài)調(diào)試一下,看看能不能打印出我們想要的內(nèi)容
(lldb) x/4gx ELPerson.class
0x100008258: 0x0000000100008230 0x0000000100357140
0x100008268: 0x000000010034f390 0x0000802c00000000
(lldb) p (cache_t \*)0x100008268 //0x100008258+0x16=0x100008268內(nèi)存偏移
(cache_t *) $1 = 0x0000000100008268
(lldb) p *$1
(cache_t) $2 = {
_bucketsAndMaybeMask = {
std::__1::atomic<unsigned long> = {
Value = 4298437520
}
}
= {
= {
_maybeMask = {
std::__1::atomic<unsigned int> = {
Value = 0
}
}
_flags = 32812
_occupied = 0
}
_originalPreoptCache = {
std::__1::atomic<preopt_cache_t *> = {
Value = 0x0000802c00000000
}
}
}
}
可以看到_maybeMask和_occupied的值都是0,我們調(diào)用一下方法,看看是不是會(huì)有變化
p [per sayNB]
2021-06-24 17:31:51.447839+0800 KCObjcBuild[79832:1703897] NB
(lldb) p *$1
(cache_t) $3 = {
_bucketsAndMaybeMask = {
std::__1::atomic<unsigned long> = {
Value = 4311894672
}
}
= {
= {
_maybeMask = {
std::__1::atomic<unsigned int> = {
Value = 7
}
}
_flags = 32812
_occupied = 1
}
_originalPreoptCache = {
std::__1::atomic<preopt_cache_t *> = {
Value = 0x0001802c00000007
}
}
}
}
(lldb) p $3->buckets()
(bucket_t *) $4 = 0x0000000101024a90
Fix-it applied, fixed expression was:
$3.buckets()
(lldb) p *$4
(bucket_t) $5 = {
_sel = {
std::__1::atomic<objc_selector *> = "" {
Value = ""
}
}
_imp = {
std::__1::atomic<unsigned long> = {
Value = 48968
}
}
}
(lldb) p $5.sel()
(SEL) $6 = "sayNB"
(lldb) p $5.imp(nil,ELPerson.class)
(IMP) $7 = 0x0000000100003d10 (KCObjcBuild`-[ELPerson sayNB])
我們發(fā)現(xiàn)方法被調(diào)用之后_maybeMask和_occupied都有值了,并且我們打印出來了緩存的方法。
cache_t::insert(SEL sel, IMP imp, id receiver)
1、獲取已緩存的容量并且+1,第一次為0
2、獲取緩存容量,不存在時(shí),需要先開辟容量,第一次開辟1<<2=4。將bucket_t *首地址存入_bucketsAndMaybeMask,將newCapacity - 1的mask存入_maybeMask,_occupied設(shè)置為0。
3、容量情況判斷,當(dāng)容量用完3/4或者7/8時(shí)(具體分架構(gòu)),需要按照2倍的大小擴(kuò)容。擴(kuò)容過程開辟新的容量,同時(shí)回收舊的容量。
3.1、這個(gè)容量是因?yàn)樨?fù)載因子問題,超過這個(gè)限制的時(shí)候,哈希沖突將會(huì)大大增加
3.2、擴(kuò)容時(shí)要清除舊的容量,開辟新的容量。第一、已經(jīng)開辟出來的內(nèi)存無法更改,所以不是原內(nèi)存簡(jiǎn)單的增加。第二、舊的內(nèi)存里面的東西如果全部移動(dòng)到新的內(nèi)存中耗費(fèi)性能,并且舊的內(nèi)存中被調(diào)用過的方法,繼續(xù)調(diào)用的概率比較低,全部移動(dòng)到新內(nèi)存不劃算。如果再次調(diào)用,會(huì)再新的內(nèi)存中緩存的。
4、通過哈希算法查找存儲(chǔ)的位置,do...while循環(huán)查找,沒有就存,不能存再哈希,一直找下去。最后不成功,調(diào)用bad_cache
4.1在arm64中cache_next會(huì)向前插入i ? i-1 : mask,其他架構(gòu)中,向后插入(i+1) & mask
void cache_t::insert(SEL sel, IMP imp, id receiver)
{
mask_t newOccupied = occupied() + 1; // 1+1
unsigned oldCapacity = capacity(), capacity = oldCapacity;
if (slowpath(isConstantEmptyCache())) {//第一次進(jìn)來為空
if (!capacity) capacity = INIT_CACHE_SIZE;//capacity = 1 << 2 = 4
reallocate(oldCapacity, capacity, /* freeOld */false);
}
else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) {
// 不超過3/4或者7/8,正常使用。用哪個(gè)分架構(gòu)
}
\#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 {// 超過3/4或者7/8,會(huì)2倍擴(kuò)容
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
if (capacity > MAX_CACHE_SIZE) {
capacity = MAX_CACHE_SIZE;
}
// 重新開辟一塊capacity * sizeof(bucket_t)大小的內(nèi)存空間,
// 將`bucket_t *`首地址存入`_bucketsAndMaybeMask`,
// 將`newCapacity - 1`的`mask`存入`_maybeMask`,
// _occupied設(shè)置為0,回收舊內(nèi)存
reallocate(oldCapacity, capacity, true);
}
//創(chuàng)建bucket_t
bucket_t *b = buckets();
mask_t m = capacity - 1; // 4-1=3
//哈希算法,存儲(chǔ)
mask_t begin = cache_hash(sel, m);
mask_t i = begin;
do {
//找到為空的地方插入,do...while循環(huán)查找
if (fastpath(b[i].sel() == 0)) {
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()
bad_cache(receiver, (SEL)sel);
#endif // !DEBUG_TASK_THREADS
}
cache流程圖:
