OC底层原理14-1:objc-msgSend缓存查找(快速查找)汇编分析
前言
我们得出结论:消息发送objc-msgSend 内部先进行 快速查找
即 缓存(CacheLookup)查找
本文主要继上一章 objc_msgSend
引申,objc_msgSend
是用汇编写的,因为性能好、速度快
汇编的特性:快 + 动态性(不确定)
objc_msgSend 汇编查找流程分析
在 objc4-781源码
中,搜索 objc_msgSend
,由于我们日常开发的都是架构 arm64
,所以需要找到 objc-msgSend-arm64.s
文件, objc_msgSend
源码实现的入口是在 ENTRY _objc_msgSend
这个文件下,发现是 汇编实现
。
- objc_msgSend 汇编源码
objc_msgSend
是消息发送的源码的入口,其使用 汇编
实现的,_objc_msgSend
源码实现如下:
1 | // 消息发送 -> 汇编入口 -> _objc_msgSend主要是拿到接收者的isa信息 |
主要有以下几个步:
【第一步】判断
objc_msgSend
方法的第一个参数receiver
是否为空如果支持
tagged pointer
小对象,跳转至LNilOrTagged
- 如果
小对象
为空,则直接返回空,即LReturnZero
- 如果
小对象
不为空,则处理小对象的isa
,走到【第二步】
- 如果
如果
既不是小对象
,receiver
也不为空,有以下两步- 从
receiver
中取出isa
存入p13
寄存器 - 通过
GetClassFromIsa_p16
中,arm64
架构下通过isa & ISA_MASK
获取shiftcls
位域的类信息,即class
,GetClassFromIsa_p16
的汇编实现如下,然后走到【第二步】
- 从
【第二步】如果获得
isa
指针- 如果有
isa
,走到CacheLookup
,即缓存查找流程,也就是所谓的sel-imp
快速查找流程
- 如果有
1 | .macro GetClassFromIsa_p16 /* src */ |
objc_msgSend 缓存(CacheLookup)查找汇编流程
1 | .macro CacheLookup |
主要分为以下几步:
【第一步】通过
cache
首地址平移16
字节(因为在objc_class中),首地址
距离cache
正好16
字节,即isa首地址
占8
字节,superClass
占8
字节),获取cache
,cache中高16位存mask
,低48位存buckets
,即p11 = cache
。【第二步】从cache中分别取出
buckets和mask
,并由mask根据哈希算法计算出哈希下标通过
cache
和掩码
(即0x0000ffffffffffff)的&
运算,将高16位mask抹零
,得到buckets指针地址,即p10 = buckets
将
cache
右移48
位,得到mask
,即p11 = mask
将
objc_msgSend
的参数p1
(即第二个参数_cmd)& mask
,通过哈希算法
,得到需要查找存储sel-imp
的bucket下标index
,即p12 = index = _cmd & mask
,为什么通过这种方式呢?因为在存储sel-imp时
,也是通过同样哈希算法计算哈希下标进行存储
,所以读取
也需要通过同样的方式读取,如下所示:
1 | // Class points to cache. SEL is key. Cache buckets store SEL+IMP. |
【第三步】根据所得的
哈希下标Index
和buckets首地址
,取出哈希下标对应的bucket
其中
PTRSHIFT
等于3
,左移4
位(即2^4 = 16字节)的目的是计算出一个bucket
实际占用的大小,结构体bucket_t
中sel
占8
字节,imp
占8
字节根据计算的哈希下标
index乘以单个bucket占用的内存大小
,得到buckets
首地址在实际内存中的偏移量
通过
首地址+实际偏移量
,获取哈希下标index对应的bucket
【第四步】根据获取的
bucket
,取出其中的imp
存入p17
,即p17 = imp
,取出sel
存入p9
,即p9 = sel
【第五步】第一次递归循环
比较获取的
bucket
中sel
与objc_msgSend
的第二个参数的_cmd
(即p1) 是否相等如果相等,则直接跳转至
CacheHit
,即缓存命中
,返回imp
如果不相等,有以下两种情况
如果一直都找不到,直接跳转至
CheckMiss
,因为$0
是normal
,会跳转至__objc_msgSend_uncached
,即进入慢速查找流程
如果
根据index获取的bucket
等于buckets的第一个元素
,则人为
的将当前bucket设置为buckets的最后一个元素
(通过buckets首地址+mask右移44位
(等同于左移4位)直接定位到bucker的最后一个元素
),然后继续进行递归循环(第一个
递归循环嵌套第二个
递归循环),即【第六步】如果
当前bucket
不等于buckets的第一个元素
,则继续向前查找
,进入第一次递归循环
【第六步】第二次递归循环:重复【第五步】的操作,与【第五步】中唯一区别是,如果
当前的bucket还是等于 buckets的第一个元素
,则直接跳转至JumpMiss
,此时的$0
是normal
,也是直接跳转至__objc_msgSend_uncached
,即进入慢速查找流程
以下是整个 快速查找
过程 值的变化
过程
objc_msgSend通过伪代码实现
1 | #include <stdio.h> |
结尾
- 在
缓存(CacheLookup)查找
过程中,如果没有找到方法实现,无论是走到CheckMiss
还是JumpMiss
,最终都会走到__objc_msgSend_uncached
汇编函数
CheckMiss源码
1 | .macro CheckMiss |
JumpMiss源码
1 | .macro JumpMiss |
- 在
objc-msg-ram64.s
文件中查找__objc_msgSend_uncached
的汇编源码,发现其核心是MethodTableLookup(即查询方法列表)
,因此如果缓存(CacheLookup)查找
没有找到,就去MethodTableLookup(即方法列表中查找)
,下一章我们介绍
__objc_msgSend_uncached源码
1 | STATIC_ENTRY __objc_msgSend_uncached |
- Post title:OC底层原理14-1:objc-msgSend缓存查找(快速查找)汇编分析
- Post author:张建
- Create time:2023-02-25 18:38:26
- Post link:https://redefine.ohevan.com/2023/02/25/OC底层原理/OC底层原理14-1:objc-msgSend缓存查找(快速查找)汇编分析/
- Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.