在上一篇文章中大概的分析了類的結構、以及類的屬性與方法的存儲,接下來咱們分析類結構中的方法緩存:cache。數組
類的結構:緩存
truct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
。。。
}
複製代碼
類的方法緩存,增長方法查找效率。bash
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
public:
struct bucket_t *buckets();
mask_t mask();
mask_t occupied();
void incrementOccupied();
void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
void initializeToEmpty();
mask_t capacity();
bool isConstantEmptyCache();
bool canBeFreed();
static size_t bytesForCapacity(uint32_t cap);
static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);
void expand();
void reallocate(mask_t oldCapacity, mask_t newCapacity);
struct bucket_t * find(cache_key_t key, id receiver);
static void bad_cache(id receiver, SEL sel, Class isa) __attribute__((noreturn));
};
複製代碼
struct bucket_t {
cache_key_t _key; // typedef unsigned long
IMP _imp;
...
};
複製代碼
bucket_t * cache_t::find(cache_key_t k, id receiver)
{
assert(k != 0);
bucket_t *b = buckets();
mask_t m = mask();
mask_t begin = cache_hash(k, m);
mask_t i = begin;
do {
if (b[i].key() == 0 || b[i].key() == k) {
return &b[i];
}
} while ((i = cache_next(i, m)) != begin);
Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
cache_t::bad_cache(receiver, (SEL)k, cls);
}
// --------------------find內部調用的cache_hash-------------------------
static inline mask_t cache_hash(cache_key_t key, mask_t mask)
{
return (mask_t)(key & mask);
}
// --------------------find內部調用的cache_next(arm64)-----------------
static inline mask_t cache_next(mask_t i, mask_t mask) {
return i ? i-1 : mask;
}
複製代碼
分析:less
獲取到buckets散列表與掩碼mask,經過cache_hash(key & mask)獲得key值對應的索引begin。函數
將begin賦值給i,方便切換索引。post
進行do-while循環查找ui
(i = cache_next(i, m)) != begin
複製代碼
好比起始下標是4, 總長度是8,依次遞減,當i = 0時,返回mask,則繼續從總長度 = 8開始查找,直到index 再次等於 4,說明已經遍歷一圈了,仍是沒找到,方法緩存查找結束。this
if (b[i].key() == 0 || b[i].key() == k)
複製代碼
用i做爲索引從散列表中取值,若是取出來的bucket_t的 key = k,則查詢成功,返回該bucket_t。spa
若是key = 0,說明在索引i的位置上尚未緩存過方法,一樣須要返回該bucket_t,用於終止緩存查詢。線程
若是此時尚未找到key對應的bucket_t,或者是空的bucket_t,則循環結束,說明查找失敗,調用bad_cache方法。
緩存填充插入函數中調用了查找方法,這個方法也是咱們須要關注的重點
static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver)
{
cacheUpdateLock.assertLocked();
// Never cache before +initialize is done
if (!cls->isInitialized()) return;
// Make sure the entry was not added to the cache by some other thread
// before we grabbed the cacheUpdateLock.
if (cache_getImp(cls, sel)) return;
cache_t *cache = getCache(cls);
cache_key_t key = getKey(sel);
// Use the cache as-is if it is less than 3/4 full
mask_t newOccupied = cache->occupied() + 1;
mask_t capacity = cache->capacity();
if (cache->isConstantEmptyCache()) {
// Cache is read-only. Replace it.
cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);
}
else if (newOccupied <= capacity / 4 * 3) {
// Cache is less than 3/4 full. Use it as-is.
}
else {
// Cache is too full. Expand it.
cache->expand();
}
// 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.
bucket_t *bucket = cache->find(key, receiver);
if (bucket->key() == 0) cache->incrementOccupied();
bucket->set(key, imp);
}
//-------------------------capacity() -----------------------------
mask_t cache_t::capacity()
{
return mask() ? mask()+1 : 0;
}
複製代碼
在確保緩存沒有被其餘線程添加
記錄對當前將要緩存的方法數mask_t newOccupied = cache->occupied() + 1;
獲取當前散列表的容量
判斷當前cache是否爲只讀的,也就是還未初始化,則從新分配緩存容量
若是newOccupied大於容量的3/4,就要進行擴容
經過find函數獲取到對應的bucket_t。
判斷返回的bucket->key() 是否爲0
若是爲0,就說明該位置上是空的,沒有緩存過方法,所以須要添加一個新的緩存。
void cache_t::incrementOccupied()
{
_occupied++;
}
複製代碼
將key與imp保存在緩存中
void cache_t::expand()
{
cacheUpdateLock.assertLocked();
uint32_t oldCapacity = capacity();
uint32_t newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE;
if ((uint32_t)(mask_t)newCapacity != newCapacity) {
// mask overflow - can not grow further
// fixme this wastes one bit of mask
newCapacity = oldCapacity;
}
reallocate(oldCapacity, newCapacity);
}
//--------------------------------------------------------
INIT_CACHE_SIZE = (1 << INIT_CACHE_SIZE_LOG2) // 4
複製代碼
當前緩存容量不存在時,開闢一個INIT_CACHE_SIZE(4)大小的緩存容量,存在則*2翻倍。
能夠看到,緩存填充插入與擴容都須要用到緩存分配函數reallocate:
void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity)
{
bool freeOld = canBeFreed();
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) {
cache_collect_free(oldBuckets, oldCapacity);
cache_collect(false);
}
}
複製代碼
用到的函數:
// 釋放判斷
bool cache_t::canBeFreed()
{
return !isConstantEmptyCache();
}
bool cache_t::isConstantEmptyCache()
{
return
occupied() == 0 &&
buckets() == emptyBucketsForCapacity(capacity(), false);
}
//---------------------------setBucketsAndMask-------------------------------
void cache_t::setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask)
{
mega_barrier();
_buckets = newBuckets;
_mask = newMask;
_occupied = 0;
}
複製代碼
判斷是否須要釋放
canBeFreed函數用來判斷當前緩存是否爲空,若是爲空,也就沒有必要釋放了。
根據容量newCapacity分配新的buckets
爲cache_t中的各個參數從新賦值,這塊是指向了一個新的內存空間
根據第一步的判斷釋放緩存
調用方法的緩存會以一個哈希散列表bucket_t的形式保存在類結構中的cache中,cache的緩存容量不存在時,開闢一個INIT_CACHE_SIZE(4)大小的緩存容量,緩存超過容量3/4時,會進行擴容*2,開闢一個新的內存空間,並釋放以前舊的緩存空間,同時保存此次的調用方法緩存。