objc_class(cache探索)

在objc_class结构体中,有一个cache_t类型,在这篇内容中,主要探索这个内容存储的数据,以及存储的规律。老样子通过内存位移先得到cache_t的内存地址。


cache_t的打印结果,看起来没有特别清楚的字段,去cache_t结构体里看看

在源码中,没具体找到相关信息,但是看到了insert方法,

我猜测插入方法,应该就是加入相应的方法缓存,因为有sel和imp,我们可以根据这个insert方法进行追踪,点进去看实现找到了一个buckts,方法内部就是操作这个buckts。


buckts

而cache_t有一个buckets()方法,进行打印
buckets打印结果

老样子,暂时看不出什么有用的信息,就去bucke_t结构体里面去瞧瞧

看一下这个capacity

unsigned oldCapacity = capacity(), capacity = oldCapacity;
capacity方法实现

继续跳下去


mask方法

那么_maybeMask = capacity - 1

那我们就继续打印一下buckts方法,看看里面的内容。


打印buckets方法

bucket_t中的sel和imp方法

继续打印


sel和imp

老规矩,看到不太明白的数据直接去源码中查找有没有对应的方法
找到了对应的sel和imp

我们可以通过查看sel方法来得到当前bucket_t指针数组中的存储内容,因为是一个指针数组类型,所以我们可以通过地址+1的方法,获取所有的存储内容.因为是散列表存储的,所以可能不连续,所以第一个+1的地址没有存储方法。
打印

打印

可以通过打印结果来看,bucket里面存储了一个class方法和一个respondsToSelector方法,既然是缓存方法,那么再调用方法之后,应该也可以打印出来,调用method1。


method1之后打印缓存

从图片来看打印结果,method1不仅没有出现,respondsToSelector也消失不见了。这就是因为缓存扩容的原因,旧的bucket被释放了,储存的数据也释放了。我们来探究一下cache扩容。

cache扩容

在insert方法中,如果缓存超出一定的临界值,就会进行扩容。


cache扩容方法

occupied()方法

可以看出返回的是cache_t的成员变量_occupied,那么首次,_occupied=0,即newOccupied = 1;

//capacity方法实现
unsigned cache_t::capacity() const
{
    return mask() ? mask()+1 : 0; 
}

//mask方法实现
mask_t cache_t::mask() const
{//_maybeMask = bucket_t的长度-1
    return _maybeMask.load(memory_order_relaxed);
}

oldCapacity = capacity();capacity = _maybeMask + 1;而 _maybeMask = bucket长度- 1;即capacity = bucket长度。

  • 反过来看第一个条件判断
if (slowpath(isConstantEmptyCache())) {//当缓存为空的时候
        // Cache is read-only. Replace it.
        if (!capacity) capacity = INIT_CACHE_SIZE;//赋一个INIT_CACHE_SIZE
        reallocate(oldCapacity, capacity, /* freeOld */false);
    }

//跳一下 INIT_CACHE_SIZE
enum {
#if CACHE_END_MARKER || (__arm64__ && !__LP64__)
    // When we have a cache end marker it fills a bucket slot, so having a
    // initial cache size of 2 buckets would not be efficient when one of the
    // slots is always filled with the end marker. So start with a cache size
    // 4 buckets.
    INIT_CACHE_SIZE_LOG2 = 2,//
#else
    // Allow an initial bucket size of 2 buckets, since a large number of
    // classes, especially metaclasses, have very few imps, and we support
    // the ability to fill 100% of the cache before resizing.
    INIT_CACHE_SIZE_LOG2 = 1,
#endif
    INIT_CACHE_SIZE      = (1 << INIT_CACHE_SIZE_LOG2),
    MAX_CACHE_SIZE_LOG2  = 16,
    MAX_CACHE_SIZE       = (1 << MAX_CACHE_SIZE_LOG2),
    FULL_UTILIZATION_CACHE_SIZE_LOG2 = 3,
    FULL_UTILIZATION_CACHE_SIZE = (1 << FULL_UTILIZATION_CACHE_SIZE_LOG2),
};

//

MAX_CACHE_SIZE_LOG2的值依赖于CACHE_END_MARKER,而CACHE_END_MARKER:在x86_64下位1,在arm64下值为0,所以MAX_CACHE_SIZE_LOG2在x86_64下位2,在arm64下为1
INIT_CACHE_SIZE在x86_64下1<<2 = 4;在arm64下1<<1为2,即bucket_t初始化的长度,在arm64家沟下位2,在x86_64下为4;初始长度计算结束后,然后调用了reallocate(oldCapacity, capacity, false); //false是是否释放掉旧的bucket
reallocate实现

ALWAYS_INLINE
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);//第一次没有旧的buckets,所以不用释放
    }
}

那么这个条件判断,我们就可以理解为在arm64架构下开辟一个长度为2的bucket,在x86_64架构下开辟一个长度为4的bucket。

  • 再看第二个条件判断
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.
    }

//cache_fill_ratio方法如下图
cache_fill_ratio

由图可得,在x86_64架构下为bucket长度的四分之三,而CACHE_END_MARKER在x86_64下=1,计算基数会+1,即长度为4的情况下,第三个方法就会存进新的筒子里;
在arm64架构下为bucket长度的八分之七,即当长度为16的时候,第十五个数据会进行扩容。
也就是这个条件判断可以理解为,在arm64架构下,如果缓存的大小小于等于bucket长度的八分之七,在x86_64架构下如果缓存的大小小于等于bucket长度的四分之三,则什么也不干。

  • 第三个条件
#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
FULL_UTILIZATION_CACHE_SIZE

FULL_UTILIZATION_CACHE_SIZE 等于1<<3=8
即在arm64架构下,当筒子的长度小于等于8的时候,什么都不做

  • 最后扩容写法
else {
        capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
        if (capacity > MAX_CACHE_SIZE) {
            capacity = MAX_CACHE_SIZE;
        }
        reallocate(oldCapacity, capacity, true);
    }

上述条件都不满足的时候,直接两倍扩容,最大值是MAX_CACHE_SIZE = 1<<16,最后reallocate传的是true,就是将旧bucket释放掉,原来在旧bucket存的值就不存在了

整个cache扩容的规则

  • 在x86_64架构下,当缓存的大小等于bucket长度的四分之三的时候(会进行+1),进行两倍扩容
  • 在arm64架构下,当缓存的大小小于等于bucket长度的半分之七的时候,进行两倍扩容,当bucket长度小于等于8的时候,不满不扩容
  • 扩容后,释放旧bucket,新bucket不存储旧bucket内容

那根据这个扩容规则,电脑架构是x86,开头遇见的问题在执行method1方法后,bucket里面只有一个class,是因为在执行method的时候,进行了扩容,所以只有一个class方法。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352

推荐阅读更多精彩内容