iOS底层-类的cache探索

2021-07-12  本文已影响0人  沉淀纷飞

前言

image

  之前的文章分析过类的本质,我们也从源码的角度看到Class的是objc_class类型的结构体,在objc_class里面有一个非常重要的变cache,那cache它到底是什么,它有什么用,它如何工作的呢?先看一段源码

cache探索

image
  由源码可以发现cachecache_t类型的。由objc_class内部的实现可以发现cache在内存中排在superclass之后,偏移16字节。后面可以通过实例验证。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;
    };
.
.//中间内容较多省略
.
    void insert(SEL sel, IMP imp, id receiver);
    void copyCacheNolock(objc_imp_cache_entry *buffer, int len);
    void destroy();
    void eraseNolock(const char *func);

    static void init();
    static void collectNolock(bool collectALot);
    static size_t bytesForCapacity(uint32_t cap);
    .
.//中间内容较多省略
.
};

结合实例分析cache的结构,先结合objc源码创建类People

#import <Foundation/Foundation.h>

@interface People : NSObject

- (void)eat;
- (void)run;
- (void)sleep;

@end

@implementation People

- (void)eat {
    NSLog(@"%s", __func__);
}

- (void)run {
    NSLog(@"%s", __func__);
}

- (void)sleep {
    NSLog(@"%s", __func__);
}

@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        People *people = [[People alloc] init];
        [people eat];
        [people run];
        [people sleep];

    }
    return 0;
}

通过LLDB指令查看People的相关输出

image
由输出可以看到$0的地址其实也是类的isa地址,我们已经知道cache在类的内存空间是首地址偏移16字节。所有$0的基础上加上0x10(16字节),又因为cachecache_t类型,输出相加后的地址得到cache。我们在输出cache,即*$1就可以得到cache的内部数据。在众多数据当中我们可以发现_maybeMask ,还有一个_occupied,这两个数据是什么,又干什么用的呢?
接着我们调用一下对象的方法[people eat],再看下上面的输出有什么不一样呢?
image
此时的输出我们可以看到_maybeMask和_occupied的值都改变了。cache_t的众多方法中inset方法着重研究下
#if __arm__  ||  __x86_64__  ||  __i386__
    #define CACHE_END_MARKER 1
#elif __arm64__ && !__LP64__
    #define CACHE_END_MARKER 0
#elif __arm64__ && __LP64__
    #define CACHE_END_MARKER 0
    #define CACHE_ALLOW_FULL_UTILIZATION 1
#else
    #error unknown architecture
#endif

void cache_t::insert(SEL sel, IMP imp, id receiver)
{
    // ...
    // 容量处理
   se the cache as-is if until we exceed our expected fill ratio.
    // occupied 为cache中方法总数 
    mask_t newOccupied = occupied() + 1;
    // 旧数据占内存的容量
    unsigned oldCapacity = capacity(), capacity = oldCapacity;//当前cache能存放方法的容量,首次进来时,值为0
    // 判断缓存内容是否为空,首次进来执行此分支内容
    if (slowpath(isConstantEmptyCache())) {
        // Cache is read-only. Replace it.
        if (!capacity)
        //新数据占内存的容量
        capacity = INIT_CACHE_SIZE;//4
        reallocate(oldCapacity, capacity, /* freeOld */false);//标记是否需要清理旧的内存空间
    }
    //插入方法后cache的bucktes内存空间中的保存的方法总数 ,CACHE_END_MARKER x86_64架构,其值为1
    // 插入方法后缓存中存放方法的总数+1小于等于缓存总容量的3/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.
    }
#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 {
    // 如果capacity值为0,赋值为INIT_CACHE_SIZE,这里等于4,否则进行原来的2倍扩容
        capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
        // 如果超过最大容量MAX_CACHE_SIZE,则赋值为最大容量
        if (capacity > MAX_CACHE_SIZE) {
            capacity = MAX_CACHE_SIZE;
        }
        // 重新开辟缓存空间,并清理之前的缓存
        reallocate(oldCapacity, capacity, true);
    }
// 插入数据
    bucket_t *b = buckets();
    mask_t m = capacity - 1;
    mask_t begin = cache_hash(sel, m);
    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)) {
        // 记录缓存方法的总数加1
            incrementOccupied();
            // 保存SEL和IMP
            b[i].set<Atomic, Encoded>(b, sel, imp, cls());
            return;
        }
        // 判断下标为i的位置存储的数据和要插入的数据是否相等
        if (b[i].sel() == sel) {
            // The entry was added to the cache by some other thread
            // before we grabbed the cacheUpdateLock.
            return;
        }
        //cache_next获取下一个下标位置,并赋值给i
    } while (fastpath((i = cache_next(i, m)) != begin));

    bad_cache(receiver, (SEL)sel);
#endif // !DEBUG_TASK_THREADS
}

从inset方法实现可以看到三个关键因素:同参数容量插入。函数方法cache_t::insert(SEL sel, IMP imp, id receiver)。参数依次为selimpreceiver类型分别是SEL(方法编号)、IMP(方法实现)、id(消息接受者)。通过insert()的实现,我们可以清晰的弄清cache_t的容量处理过程。需要注意以下几点:

探究数据插入过程

mask_t cache_t::occupied() const
{
    return _occupied;
}

unsigned cache_t::capacity() const
{
    return mask() ? mask()+1 : 0; 
}

mask_t cache_t::mask() const
{
    return _maybeMask.load(memory_order_relaxed);
}

由occupied()的源码可以看到occupied()的返回值为_occupied。在insert()中调用了capacity()。capacity()实现中又调用了mask()。mask()是从内存中读取_maybeMask的值。

reallocate探索

void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld)
{
    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) {
        collect_free(oldBuckets, oldCapacity);
    }
}

reallocate()的作用是重新开辟缓存空间,并清理之前的缓存。在调用setBucketsAndMask(newBuckets, newCapacity - 1)时,保存了_bucketsAndMaybeMask和_maybeMask的值。当freeOld成立时,会调用collect_free()清理掉旧的缓存值。

buckets()探索

struct bucket_t *cache_t::buckets() const
{
// 读取_bucketsAndMaybeMask的内存地址
    uintptr_t addr = _bucketsAndMaybeMask.load(memory_order_relaxed);
    return (bucket_t *)(addr & bucketsMask);
}

struct bucket_t {
    explicit_atomic<SEL> _sel;
    explicit_atomic<uintptr_t> _imp;
    // ...
};

buckets()的源码可以看到_bucketsAndMaybeMask的内存地址&bucketsMask,返回bucket_t *类型的数据,即存放bucket_t类型数据的连续存储空间。bucket_t中保存的就是SEL和IMP。

setBucketsAndMask()探索

void cache_t::setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask)
{
    _bucketsAndMaybeMask.store((uintptr_t)newBuckets, memory_order_release);
    _maybeMask.store(newMask, memory_order_release);
    _occupied = 0;
}

setBucketsAndMask()的源码可见,此处保存了_bucketsAndMaybeMask_maybeMask的值。

总结

上一篇 下一篇

猜你喜欢

热点阅读