cache_t探究

前言

 

类的组成如下图:

 本文来探究cache_t,接下来进入正题。

cache_t源码

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
    };

#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    // _bucketsAndMaybeMask is a buckets_t pointer
    // _maybeMask is the buckets mask

    static constexpr uintptr_t bucketsMask = ~0ul;
    static_assert(!CONFIG_USE_PREOPT_CACHES, "preoptimized caches not supported");
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
    static constexpr uintptr_t maskShift = 48;
    static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
    static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << maskShift) - 1;
    
    static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS, "Bucket field doesn't have enough bits for arbitrary pointers.");
#if CONFIG_USE_PREOPT_CACHES
    static constexpr uintptr_t preoptBucketsMarker = 1ul;
    static constexpr uintptr_t preoptBucketsMask = bucketsMask & ~preoptBucketsMarker;
#endif
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    // _bucketsAndMaybeMask is a buckets_t pointer in the low 48 bits
    // _maybeMask is unused, the mask is stored in the top 16 bits.

    // 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;

。。。。。。

cache_t在不同的硬件上结构还不一样,下面分析真机64位处理器即CACHE_MASK_STORAGE_HIGH_16的cache_t

cache_t组成如下图

 bucket_t主要是selimp,源码如下

struct bucket_t {
private:
    // IMP-first is better for arm64e ptrauth and no worse for arm64.
    // SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
    explicit_atomic<uintptr_t> _imp;
    explicit_atomic<SEL> _sel;
#else
    explicit_atomic<SEL> _sel;
    explicit_atomic<uintptr_t> _imp;
#endif

。。。

cache_t探索

1.lldb打印

🌰代码

#import <Foundation/Foundation.h>
@interface SLPerson : NSObject
@property(nonatomic, copy)NSString * lgName;
@property(nonatomic, strong)NSString * nickName;
-(void)sayHello;
-(void)sayCode;
-(void)sayMaster;
-(void)sayNB;
+(void)sayHappy;
@end

@implementation SLPerson
- (void)sayHello{
    NSLog(@"SLPerson say : %s", __func__);
}
- (void)sayCode{
    NSLog(@"SLPerson say : %s", __func__);
}
- (void)sayMaster{
    NSLog(@"SLPerson say : %s", __func__);
}
- (void)sayNB{
    NSLog(@"SLPerson say : %s", __func__);
}
+(void)sayHappy{
    NSLog(@"SLPerson say : %s", __func__);
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        SLPerson * p = [SLPerson alloc];
        Class pClass = [SLPerson class];
        
        [p sayHello];
        [p sayCode];
        [p sayMaster];
        
        NSLog(@"%@", pClass);
    }
    return 0;
}
(lldb) x/4gx pClass
0x100008258: 0x0000000100008230 0x00000001006dd140
0x100008268: 0x0000600002c0c080 0x0001802400000007
(lldb) p (cache_t *)0x00000001006dd150
(cache_t *) $1 = 0x00000001006dd150
(lldb) p *$1
(cache_t) $2 = {
  _bucketsAndMaybeMask = {
    std::__1::atomic<unsigned long> = {
      Value = 105553140417216
    }
  }
   = {
     = {
      _maybeMask = {
        std::__1::atomic<unsigned int> = {
          Value = 3
        }
      }
      _flags = 32784
      _occupied = 2
    }
    _originalPreoptCache = {
      std::__1::atomic<preopt_cache_t *> = {
        Value = 0x0002801000000003
      }
    }
  }
}
(lldb) p $2.buckets()
(bucket_t *) $3 = 0x00006000017082c0
(lldb) p *$3
(bucket_t) $4 = {
  _sel = {
    std::__1::atomic<objc_selector *> = "" {
      Value = ""
    }
  }
  _imp = {
    std::__1::atomic<unsigned long> = {
      Value = 106176
    }
  }
}
(lldb) p $4.sel()
(SEL) $5 = "init"
(lldb) p $4.imp(&$4,pClass)
(IMP) $6 = 0x0000000100011c98
(lldb) 

buckets是一个数组,所以不止一个缓存方法

(lldb)  p $2.buckets()[1]
(bucket_t) $7 = {
  _sel = {
    std::__1::atomic<objc_selector *> = "" {
      Value = ""
    }
  }
  _imp = {
    std::__1::atomic<unsigned long> = {
      Value = 106224
    }
  }
}
(lldb) p $7.sel()
(SEL) $8 = "dealloc"

2.脱离objc源码探索

🌰代码

typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits

struct sl_bucket_t {
    SEL _sel;
    IMP _imp;
};

struct sl_cache_t {
    struct sl_bucket_t * _buckets;
    mask_t _mask;
    uint16_t _flags;
    uint16_t _occupied;
};

struct sl_class_data_bits_t {
    uintptr_t bits;
};

struct sl_objc_class {
    Class ISA;
    Class superclass;
    struct sl_cache_t cache;             // formerly cache pointer and vtable
    struct sl_class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
};


@interface SLPerson : NSObject
@property (nonatomic, copy) NSString *lgName;
@property (nonatomic, strong) NSString *nickName;
- (void)say1;
- (void)say2;
- (void)say3;
- (void)say4;
- (void)say5;
- (void)say6;
- (void)say7;
+ (void)sayHappy;
@end

@implementation SLPerson
- (void)say1{
    NSLog(@"SLPerson say : %s",__func__);
}
- (void)say2{
    NSLog(@"SLPerson say : %s",__func__);
}
- (void)say3{
    NSLog(@"SLPerson say : %s",__func__);
}
- (void)say4{
    NSLog(@"SLPerson say : %s",__func__);
}
- (void)say5{
    NSLog(@"SLPerson say : %s",__func__);
}
- (void)say6{
    NSLog(@"SLPerson say : %s",__func__);
}
- (void)say7{
    NSLog(@"SLPerson say : %s",__func__);
}
+ (void)sayHappy{
    NSLog(@"SLPerson say : %s",__func__);
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        SLPerson *p  = [SLPerson alloc];
        Class pClass = [SLPerson class];  // objc_clas
        [p say1];
        [p say2];
        //[p say3];
        //[p say4];

        struct sl_objc_class *sl_pClass = (__bridge struct sl_objc_class *)(pClass);
        NSLog(@"%hu - %u",sl_pClass->cache._occupied,sl_pClass->cache._mask);
        for (mask_t i = 0; i<sl_pClass->cache._mask; i++) {
            // 打印获取的 bucket
            struct sl_bucket_t bucket = sl_pClass->cache._buckets[i];
            NSLog(@"%@ - %p",NSStringFromSelector(bucket._sel),bucket._imp);
        }

        
        NSLog(@"Hello, World!");
    }
    return 0;
}

打印如下

2023-02-08 15:14:38.367995+0800 KCObjcBuild[3413:71657] SLPerson say : -[SLPerson say1]
2023-02-08 15:14:38.368470+0800 KCObjcBuild[3413:71657] SLPerson say : -[SLPerson say2]
2023-02-08 15:14:38.368530+0800 KCObjcBuild[3413:71657] 2 - 3
2023-02-08 15:14:38.368573+0800 KCObjcBuild[3413:71657] (null) - 0x0
2023-02-08 15:14:38.368683+0800 KCObjcBuild[3413:71657] say1 - 0xb818
2023-02-08 15:14:38.368749+0800 KCObjcBuild[3413:71657] say2 - 0xb828
2023-02-08 15:14:38.368783+0800 KCObjcBuild[3413:71657] Hello, World!
Program ended with exit code: 0

去掉注释,调用say3和say4,输出如下

2023-02-08 15:18:01.703909+0800 KCObjcBuild[3453:74143] SLPerson say : -[SLPerson say1]
2023-02-08 15:18:01.704488+0800 KCObjcBuild[3453:74143] SLPerson say : -[SLPerson say2]
2023-02-08 15:18:01.704598+0800 KCObjcBuild[3453:74143] SLPerson say : -[SLPerson say3]
2023-02-08 15:18:01.704660+0800 KCObjcBuild[3453:74143] SLPerson say : -[SLPerson say4]
2023-02-08 15:18:01.704707+0800 KCObjcBuild[3453:74143] 2 - 7
2023-02-08 15:18:01.704750+0800 KCObjcBuild[3453:74143] (null) - 0x0
2023-02-08 15:18:01.704857+0800 KCObjcBuild[3453:74143] say3 - 0xb868
2023-02-08 15:18:01.704894+0800 KCObjcBuild[3453:74143] (null) - 0x0
2023-02-08 15:18:01.704925+0800 KCObjcBuild[3453:74143] (null) - 0x0
2023-02-08 15:18:01.704954+0800 KCObjcBuild[3453:74143] (null) - 0x0
2023-02-08 15:18:01.704984+0800 KCObjcBuild[3453:74143] (null) - 0x0
2023-02-08 15:18:01.705041+0800 KCObjcBuild[3453:74143] say4 - 0xb858
2023-02-08 15:18:01.705075+0800 KCObjcBuild[3453:74143] Hello, World!
Program ended with exit code: 0

发现随着方法增多,_mask会变化,在源码中发现有个方法incrementOccupied

void incrementOccupied();

void cache_t::incrementOccupied() 
{
    _occupied++;
}

这个函数在cache_tinsert方法有调用

下面分析insert代码,主要分为几个部分:

  • 计算当前缓存占用量
  • 根据缓存占用量判断执行的操作
  • 针对需要存储的bucket进行内部imp和sel赋值 

a. 算当前缓存占用量

mask_t newOccupied = occupied() + 1;

对象被创建后occupied在重新调用init属性赋值(调set方法)、其他方法调用occupied都会增加,总的来说就是在调用方法的时候occupied增加。

b.根据缓存占用判断要执行的操作

第一次创建,开辟4个容量 

if (slowpath(isConstantEmptyCache())) {
        // Cache is read-only. Replace it.
        if (!capacity) capacity = INIT_CACHE_SIZE;
        reallocate(oldCapacity, capacity, /* freeOld */false);
    }

容量小于等于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.
    }
// Historical fill ratio of 75% (since the new objc runtime was introduced).
static inline mask_t cache_fill_ratio(mask_t capacity) {
    return capacity * 3 / 4;
}

 超过3/4,则两倍扩容重新开辟空间

else {
        capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
        if (capacity > MAX_CACHE_SIZE) {
            capacity = MAX_CACHE_SIZE;
        }
        reallocate(oldCapacity, capacity, true);
    }

c.针对需要存储的bucket进行内部imp和sel赋值 

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)) {
            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));

这部分主要根据哈希算法(cache_hash),计算sel-imp存储的哈希下标。当前哈希下标存储的sel 不等于 即将插入的sel,则通过哈希冲突算法(cache_next),重新进行哈希计算,得到新的下标,再去对比进行存储。

哈希算法

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);
}

哈希冲突算法

#if CACHE_END_MARKER
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return (i+1) & mask;
}
#elif __arm64__
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return i ? i-1 : mask;
}
#else

总结 

  • _mask——掩码数据:在哈希算法或者哈希冲突算法中计算哈希下标,其中mask 等于capacity - 1
  • _occupied——哈希表中sel-imp的个数,方法调用会使_occupied变化

cache分析流程如下(图片转载自一荤一素一碗粥,感谢🙏)

​​​​​​​​​​​​​​

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值