The underlying structure of the cache:
Is a hash table with the corresponding element bucket_t, with an initial size of 2 to the 1st and a maximum size of 2 to the 16th
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;
};
// The value of capacity -1
int32_t mask(a) const
{
return _bucketsAndMaybeMask >> maskShift;
}
// Buckets array address
struct bucket_t *buckets(a) const
{
return (bucket_t*)(_bucketsAndMaybeMask & bucketsMask); }}struct bucket_t {
explicit_atomic<uintptr_t> _imp;
explicit_atomic<SEL> _sel;
}
Copy the code
Hash algorithm:
The address of the SEL, xor moves 7 bits to the right of itself, and the hash table (capacity -1) does &
Ps: If the capacity is 2 to the m, then the & operation with (capacity -1) is equal to % capacity
Hash conflict:
Use the hash algorithm to calculate the subscript. If the subscript has a value, run the subscript down to -1. When the subscript decreases to 0, run the subscript down to -1 until an empty position is found, and then put it in
Hash table expansion:
Capacity expansion conditions:
Check whether the current capacity is greater than 8. If the capacity is greater than 8, the loading factor is greater than 7/8 and the capacity is expanded; if the capacity is less than or equal to 8, the capacity is expanded after the loading factor is equal to 1
Loading factor: (used space/total space)
Scale operation
Create a new space with an old size of 2, at which point the old cache is thrown away
Source:
/ / basic macros
INIT_CACHE_SIZE_LOG2 = 1,
INIT_CACHE_SIZE = (1 << INIT_CACHE_SIZE_LOG2), // Initial size
MAX_CACHE_SIZE_LOG2 = 16,
MAX_CACHE_SIZE = (1 << MAX_CACHE_SIZE_LOG2), // Maximum size
// Determine whether expansion is required
static inline mask_t cache_fill_ratio(mask_t capacity) {
return capacity * 7 / 8;
}
// Resolve hash conflicts
static inline mask_t cache_next(mask_t i, mask_t mask) {
return i ? i- 1 : mask;
}
// Hash algorithm
static inline mask_t cache_hash(SEL sel, mask_t mask)
{
uintptr_t value = (uintptr_t)sel;
#if CONFIG_USE_PREOPT_CACHES
value ^= value >> 7;
#endif
return (mask_t)(value & mask);
}
/ / capacity
void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld)
{
bucket_t *oldBuckets = buckets(a);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) {
collect_free(oldBuckets, oldCapacity); }}// Insert a new cache
void cache_t::insert(SEL sel, IMP imp, id receiver)
{
runtimeLock.assertLocked(a);// Never cache before +initialize is done
if (slowpath(!cls() - >isInitialized())) {
return;
}
if (isConstantOptimizedCache()) {
_objc_fatal("cache_t::insert() called with a preoptimized cache for %s".cls() - >nameForLogging());
}
#if DEBUG_TASK_THREADS
return _collecting_in_critical();
#else
#if CONFIG_USE_CACHE_LOCK
mutex_locker_t lock(cacheUpdateLock);
#endif
ASSERT(sel ! =0 && cls() - >isInitialized());
// 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 is read-only. Replace it.
if(! capacity) capacity = INIT_CACHE_SIZE;reallocate(oldCapacity, capacity, /* freeOld */false);
}
// Determine whether the capacity needs to be expanded (whether the used space is greater than 7/8 of the capacity; if the space is larger than 7/8 of the capacity, the capacity needs to be expanded; if the space is smaller than 7/8 of the capacity, the capacity does not need to be expanded)
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.
}
#if CACHE_ALLOW_FULL_UTILIZATION
// Before capacity expansion, the system checks whether the capacity is less than or equal to 8. If the capacity is less than or equal to 8, the capacity can be used up
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 {
// Create a new space
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
if (capacity > MAX_CACHE_SIZE) {
capacity = MAX_CACHE_SIZE;
}
reallocate(oldCapacity, capacity, true);
}
bucket_t *b = buckets(a);mask_t m = capacity - 1;
// Hash algorithm
mask_t begin = cache_hash(sel, m);
mask_t i = begin;
// Resolve hash conflicts
// Scan for the first unused slot and insert there.
// There is guaranteed to be an empty slot.
do {
if (fastpath(b[i].sel() = =0)) {
incrementOccupied(a); 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
}
Copy the code
DEMO
#import "ViewController.h"
#import <objc/runtime.h>
// How much the mask is shifted by.
static constexpr uintptr_t maskShift = 48;
// Additional bits after the mask which must be zero. msgSend
// takes advantage of these additional bits to construct the value
// `mask << 4` from `_maskAndBuckets` in a single instruction.
static constexpr uintptr_t maskZeroBits = 4;
// The largest mask value we can store.
static constexpr uintptr_t maxMask = ((uintptr_t)1< < (64 - maskShift)) - 1;
// The mask applied to `_maskAndBuckets` to retrieve the buckets pointer.
static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;
struct wy_objc_object {
void* isa;
};
struct bucket_t {
uintptr_t _imp;
SEL _sel;
};
struct wy_cache_t {
uintptr_t _bucketsAndMaybeMask;
union {
struct {
int32_t _maybeMask;
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
};
void * _originalPreoptCache;
};
int32_t mask() const
{
return _bucketsAndMaybeMask >> maskShift;
}
struct bucket_t *buckets() const
{
return(bucket_t *)(_bucketsAndMaybeMask & bucketsMask); }};struct wy_objc_class: wy_objc_object {
// Class ISA;
wy_objc_class *superclass; // Parent pointer
wy_cache_t cache; // Method cache
uintptr_t bits; / / class information
};
@interface Person : NSObject
@end
@implementation Person
- (void)categoryTest {
NSLog(@"+ test");
}
- (void)categoryTest1 {
NSLog(@"+ test");
}
- (void)categoryTest2 {
NSLog(@"+ test");
}
- (void)categoryTest3 {
NSLog(@"+ test");
}
@end
@interface ViewController(a)
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
struct wy_objc_class *cls = (__bridge struct wy_objc_class *)[Person class];
struct bucket_t *buckets = cls->cache.buckets();
int32_t mask = cls->cache.mask();
NSLog(@" %d ----------------", mask);
class_getInstanceMethod([Person class].@selector(categoryTest));
buckets = cls->cache.buckets();
mask = cls->cache.mask();
for(int i = 0; i <= mask; i++) {
NSLog(@ "% @".NSStringFromSelector((buckets + i)->_sel) );
}
NSLog(@" %d ----------------", mask);
class_getInstanceMethod([Person class].@selector(categoryTest1));
buckets = cls->cache.buckets();
mask = cls->cache.mask();
for(int i = 0; i <= mask; i++) {
NSLog(@ "% @".NSStringFromSelector((buckets + i)->_sel) );
}
NSLog(@" %d ----------------", mask);
class_getInstanceMethod([Person class].@selector(categoryTest2));
buckets = cls->cache.buckets();
mask = cls->cache.mask();
for(int i = 0; i <= mask; i++) {
NSLog(@ "% @".NSStringFromSelector((buckets + i)->_sel) );
}
NSLog(@" %d ----------------", mask);
}
@end
/ * * 2021-11-04 14:51:50. 790030 + 0800 CacheApp [29923-8662404] 0 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - the 2021-11-04 14:51:50. 790074 + 0800 CacheApp[29923:8662404] (NULL) 2021-11-04 14:51:50.790100+0800 CacheApp[29923:8662404] categoryTest 2021-11-04 14:51:50.790117+0800 CacheApp[29923:8662404] 1 ---------------- 2021-11-04 14:51:50.790136+0800 CacheApp[29923:8662404] Catitest1 2021-11-04 14:51:50.790157+0800 CacheApp[29923:8662404] CatiTEST 2021-11-04 14:51:50.790171+0800 CacheApp[29923:8662404] 1 ---------------- 2021-11-04 14:51:50.790186+0800 CacheApp[29923:8662404] (NULL) 2021-11-04 14:51:50.790201+0800 CacheApp[29923:8662404] (NULL) 2021-11-04 14:51:50.790267+0800 CacheApp[29923:8662404] CategoryTest2 2021-11-04 14:51:50.790329+0800 CacheApp[29923:8662404] (NULL) 2021-11-04 14:51:50.790391+0800 CacheApp[29923:8662404] 3 ---------------- */
Copy the code