This is the 6th day of my participation in the August More Text Challenge
In the chapter of class principle analysis, we have learned about the structure of class and analyzed the bits structure, and also mentioned cache. Today, let’s take a closer look at cache
Cache data structure
cache
That’s the cache, the cache of the class. How do you cache it? Take a look at it with the questioncache_t
Data structure ofthroughlldb
Breakpoint debugging, first address translation16 bytes
namely0x10
You can getcacht_t
Discovery and source codecache_t
Is structurally consistent. So what are the member variables in the structurecache_t
What’s the point?
First of all, we know that the operation on cache is add, delete, change, check. See if there are any other key methods or operations in the cache_t structure. The INSERT method is found in the public: method of cache_t.
void insert(SEL sel, IMP imp, id receiver);
Copy the code
insert
As the name impliesinsert
, then enterinsert
methods insert
In the method, yesbucket
Operation, continue to enterbucket_t
bucket_t
There are some familiar onesIMP
andSEL
, the obvious caching method inbucket_t
In the
Finally, the following structure diagram is obtained:
LLDB analyzes the cache bottom layer
It was analyzed abovecache
And finally find the cachebucket_t
Medium, then pass nowlldb
Debug and verifyFirst getcache_t
In the_bucketsAndMaybeMask
In the access to_bucketsAndMaybeMask
In theValue
When can not get. And then we hadcache_t
Find the following method in the structure
struct bucket_t *buckets() const;
Copy the code
To obtaincache_t
In thebuckets()
Find what we want_sel
and_imp
. But none of these values are herenull
is0
It just has no value. Because the current method has not executed so no cache, modify the code to call the method after debuggingThe results ofbuckets
It still doesn’t have a value, butmask
andoccupied
There is a change in, indicating that there should be cache. whybuckets
Or not?
You can see the definition abovebuckets
Is a structure pointer that prints directlybuckets()
Get the first address, need to usebuckets()[index]
Address offset obtain otherbucket
And then finally I find something that has value at subscript 2bucket
Through thebucket_t
thesel
Method prints the name of the method that was just called.
Analysis of the underlying principle of cache
We know that cache has to be written in before it can be read. We’ve just seen an insert method. So let’s start with this method
void insert(SEL sel, IMP imp, id receiver);
Copy the code
The arguments passed to the INSERT method are SEL, IMP, and receiver, respectively. Source code analysis:
void cache_t::insert(SEL sel, IMP imp, id receiver)
{
...
// Use the cache as-is if until we exceed our expected fill ratio.
mask_t newOccupied = occupied() + 1;// Prepare for the first time; newOccupied = 1
unsigned oldCapacity = capacity(), capacity = oldCapacity;// _maybeMask + 1, the first input is 0
if (slowpath(isConstantEmptyCache())) { // Empty cache, first entry
// Cache is read-only. Replace it.
if(! capacity) capacity = INIT_CACHE_SIZE;// The default value is 4
reallocate(oldCapacity, capacity, /* freeOld */false);// Create capacity
}
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 the cache capacity is less than or equal to 3/4 or 7/8 of capacity (different architectures), do nothing and continue
}
#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.
// If capacity <= 8 and newOccupied + 1/0 <= capacity, 100% of the cache is allowed
}
#endif
else {
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;// The value of capacity is doubled. Otherwise, the value is 4
if (capacity > MAX_CACHE_SIZE) {
capacity = MAX_CACHE_SIZE;
}
reallocate(oldCapacity, capacity, true);// Create new memory and free up old space
}
bucket_t *b = buckets();// Get buckets() first address
mask_t m = capacity - 1;// mask = capacity - 1
mask_t begin = cache_hash(sel, m);// Get the hash subscript
mask_t i = begin;
// Scan for the first unused slot and insert there.
// There is guaranteed to be an empty slot.
do {
if (fastpath(b[i].sel() == 0)) { / / the bucket is empty
incrementOccupied(); // Occupied + 1
b[i].set<Atomic, Encoded>(b, sel, imp, cls()); // Put SEL and IMP into bucket
return;
}
if (b[i].sel() == sel) { // Bucket already exists and will not be processed
// 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));// Prevent hash collisions. Hash again. Does not equal the subscript begin. }Copy the code
We created newOccupied first. Occupied; occupied; occupied; occupied; occupied; occupied; occupied; occupied; occupied
mask_t cache_t::occupied() const
{
return _occupied;
}
Copy the code
OldCapacity and capacity are _maybeMask + 1 or 0:
unsigned cache_t::capacity() const
{
return mask() ? mask()+1 : 0;
}
mask_t cache_t::mask() const
{
return _maybeMask.load(memory_order_relaxed);
}
Copy the code
reallocate
Methods:
ALWAYS_INLINE
void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld)
{
bucket_t *oldBuckets = buckets();/ / get oldBuckets
bucket_t *newBuckets = allocateBuckets(newCapacity);// Get newBuckets
// 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);
// Set Buckets to newBuckets and Mask to newcapacity-1
setBucketsAndMask(newBuckets, newCapacity - 1);
if (freeOld) {
// Free the old memory, i.e. clear the previous cachecollect_free(oldBuckets, oldCapacity); }}Copy the code
allocateBuckets
Open up memorysetBucketsAndMask
Set up thebuckets
andmask
collect_free
Determine whether to release old memory (during capacity expansion)
allocateBuckets
Methods:
bucket_t *cache_t::allocateBuckets(mask_t newCapacity)
{
// Allocate one extra bucket to mark the end of the list.
// This can't overflow mask_t because newCapacity is a power of 2.
bucket_t *newBuckets = (bucket_t *)calloc(bytesForCapacity(newCapacity), 1);// Set up a new bucket
bucket_t *end = endMarker(newBuckets, newCapacity);// Take the last one from your new buckets
#if __arm__
// End marker's sel is 1 and imp points BEFORE the first bucket.
// This saves an instruction in objc_msgSend.
end->set<NotAtomic, Raw>(newBuckets, (SEL)(uintptr_t)1, (IMP)(newBuckets - 1), nil);
#else
// End marker's sel is 1 and imp points to the first bucket.
// sel = 1, imp= the first address of the newBuckets
end->set<NotAtomic, Raw>(newBuckets, (SEL)(uintptr_t)1, (IMP)newBuckets, nil);
#endif
if (PrintCaches) recordNewCache(newCapacity);
return newBuckets;
}
Copy the code
calloc
Create a memoryend->set
Store the last location of open memorysel
=1
.imp
=The address of the first buket location
setBucketsAndMask
Methods:
void cache_t::setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask)
{
// objc_msgSend uses mask and buckets with no locks.
// It is safe for objc_msgSend to see new buckets but old mask.
// (It will get a cache miss but not overrun the buckets' bounds).
// It is unsafe for objc_msgSend to see old buckets and new mask.
// Therefore we write new buckets, wait a lot, then write new mask.
// objc_msgSend reads mask first, then buckets.
#ifdef __arm__
// ensure other threads see buckets contents before buckets pointer
mega_barrier();
_bucketsAndMaybeMask.store((uintptr_t)newBuckets, memory_order_relaxed);
// ensure other threads see new buckets before new mask
mega_barrier();
_maybeMask.store(newMask, memory_order_relaxed);
_occupied = 0;
#elif __x86_64__ || i386
// ensure other threads see buckets contents before buckets pointer
// _bucketsAndMaybeMask stores newBuckets as an address pointer
_bucketsAndMaybeMask.store((uintptr_t)newBuckets, memory_order_release);
// ensure other threads see new buckets before new mask
// _maybeMask is saved to newMask
_maybeMask.store(newMask, memory_order_release);
_occupied = 0;
#else
#error Don't know how to do setBucketsAndMask on this architecture.
#endif
}
Copy the code
_bucketsAndMaybeMask.store()
Store data to _bucketsAndMaybeMask_maybeMask.store()
Store data to _maybeMask
Cache_t Flowchart
The overall flow of cache_T (borrow from kc teacher 😁)