RunTime-ISA底层了解

1.Runtime的简介

Runtime是一套C语言的API,封装了很多动态性相关的函数;OC的动态性就是由Runtime来支撑和实现的。

平时编写的OC代码,底层都是转换成了Runtime API进行调用。

具体应用:

  • 利用关联对象给分类添加属性
  • 遍历类的所有成员属性,动态修成其属性值(例如,修改textFile的占位文字颜色、字典转模型、自动归档解挡)
  • 交换方法的实现
  • 利用消息转发机制解决方法找不到的异常问题
  • kvo和kvc等

2.isa详解

要学习runtime,首先要了解它底层的一些常用数据结构,比如isa指针。在arm64架构之前,isa就是一个普通的指针,存储着class、Meta-Class对象的内存地址;从arm64架构开始,对isa指针进行了优化,变成了一个共用体(union)结构体,还是用位域来存储更多的信息。

(1)通过源码查看arm64架构之前的isa类型,可以看到是class类型的,如下图:

查看arm64结构之后的isa类型,可以看到是isa_t类型的,如下图:

进入isa_t查看里面存放的类型,如下图:

从上图可以看出,如果是arm64的架构,就采用了优化后的isa类型了;并且是通过位域的运算方式来获取值的。至于位域的运算方式可以自行百度。其中isa占用了8个字节,每个字节由8位,所以isa占用了64位,从上图中,我们可以将1+1+33+6+1+1+1+19 算出刚好等于64位。现在,我们需要了解一下优化后的isa位域中每一位都表示了什么含义:

 

从上个表中,看到如果没有has_assoc和has_cxx_dtor会释放的更快,为什么这么说呢?找到objc的析构函数(也就是销毁的函数)的源码,如下:

/***********************************************************************
* objc_destructInstance
* Destroys an instance without freeing memory. 
* Calls C++ destructors.
* Calls ARC ivar cleanup.
* Removes associative references.
* Returns `obj`. Does nothing if `obj` is nil.
**********************************************************************/
void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}

从源码中,我们可以知道,如果有C++的函数就调用object_cxxDestruct()函数来清楚C++的函数;

如果有关联对象就调用_object_remove_assocations()函数来清除关联的对象;

如果都没有,直接调用释放的函数,所以如果没有has_assoc和has_cxx_dtor会对象会释放的更快。

(2)需要注意的一点是, 如果用从isa中取出class的地址信息,怎么取呢?

首先,我们知道优化后的isa是共用体,通过位域运算获取值的,那么如果要通过位域运算获取class的地址信息的话,需要把isa的值&一个ISA_MASK的码,如下图:

方法如下:

其中Class的类型定义如下:

3.Class的结构

先看一张图:

上图主要是从优化后的结构说的!!!

3.1 class_rw_t 和 class_ro_t的结构

从上图中,可以看出objc_class 存放了一个class_data_bits_t的结构体的字段bits,这个字段用于获取具体的类信息。这个字段通过&FAST_DATA_MASK来获取class_rw_t结构体的数据信息,源码如下:

点击查看bits.data()这个方法,源码如下:

点击class_rw_t,查看class_rw_t结构体存放的信息如下:

点击查看class_ro_t的结构体如下:

class_rw_t 里面的methods、properties和protocols 数二维数组,可读可写。点击查看class_rw_t里面的method_array_t的结构体如下:

可以看出method_array_t类型的methods是二维数组,可读可写,包含了类初始化内容和分类的内容,二维数组放的数据结构如下:

从上图可以看出,methods的数组是先存放method_list_t 类型的数组,method_list_t 存放的是method_t结构类型的数组。以此类推properties和protocols存放的数据结构。

class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容。

问题来了,既然class_rw_t 里有 methodlist、protocols这些数据,class_ro_t 里面也有baseMethodsList等这些数据,那么这两种有什么区别呢?

先看源码:

通过源码可以看出,当创建实例对象时,是先把类中的属性和方法先存放到class_ro_t里面的,当有分类时,才会把class_ro_t里面的数据复制一份放到class_rw_t里面的methods里面去。

3.2 method_t的了解

从3.class_rw_t和class_ro_t中的源码查看,需要知道的是:method_t是对方法\函数的封装,method_t的结构体通过源码可查看到如下图:

(1)SEL代表方法(函数名),一般叫做选择器,底层结构跟char *类似。

  • 可以通过@selector()或者sel_registerName()获得;
  • 可以通过sel_getName()和NSStringFromSeletor()转成字符串
  • 不同类中相同名字的方法,所对应的方法选择器是相同的

(2)IMP代表了函数的具体实现

typedef id -Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ....)

(3)types  类型编码(types EnCoding):包含了返回值类型和参数的类型

为了便于理解上面的types中的类型编码,请前往这里了解一下类型编码。

4.cache_t的结构体

了解完class_rw_t 的结构体后,就要看看cacht_t的结构体了。Class内部结构中有个方法缓存(cache_t),用散列表来缓存曾经调用过的方法,可以提高方法的查找速度:

这里的cache 是指类的方法缓存。

4.1 为什么要有方法缓存?

我们知道,方法的调用其实从通过isa指针找到类对象的方法列表,在类对象的方法列表中如果没有找到该方法,就通过superClass找父类的方法列表是否有这个方法,如果没有的话,继续往上找,直到根类的方法列表也没有才会结束;但是如果找到了就会直接返回。如下图:

但是这样查看的方式性能略差;假设我的方法就是在根类中方法列表里,那么每次都要通过子类到父类再到父类...再到根类这样找,所以才有了方法缓存的产生。当找到方法时,把方法放到缓存里。所以当调用一个方法时,先通过isa指针找到该类,先从该类的缓存方法列表中查找,如果缓存列表中没有找到,再通过子类到父类再到父类...再到根类这样方式查找。

4.2 cache_t的结构体了解

cache_t内部结构体如下图:

查看bucket_t的结构体如下:

从上两图可以查看,方法缓存是通过以@selector(函数名)为key,以函数的地址为value放到散列表的数组中。

4.3散列表的了解

1.什么是散列表?

散列表就是数组,有两个元素,第一个元素数是序列号(也就是索引),第二个元素就是存放的值。

2. 散列表为什么会非常的高效?
因为散列表通过存储的值&_mask的到一个索引,这个所以就是序列表,通过序列表直接取值,这样就不用一次一次的遍历数组了。因为例如算出来的索引是4,那么4之前的索引可能都是NULL,所以,散列表(哈希表)是以空间换时间 的方法.

3. bucket_t散列表的形式大概如下:

左边是索引, 右边是 bucket_t 结构体

如上图所示, bucket_t包括 _key 与 IMP, _key 就是SEL

4.iOS arm64 散列表存储原理:

  • 初始时, 为对象的cach_t分配一个空间, 值为NULL
  • 调用方法时, 为对象发送一个 SEL 消息, 如 @selector(personTest), 将这个方法缓存
  • 系统用 SEL 与 _mask 作按位与计算: @selector(personTest) & _mask , 假设其值==2,
  • 检查索引2 对应的空间是否为NULL , 如果为NULL 就将这个bucket_t 缓存在索引2对应空间
  • 果不为空, 索引减1, 再检查是否为NULL, 依次类推. 如果索引<0, 则使索引 =_mask - 1, 直至找到索引对应空间为NULL, 再缓存

5. 对应的查找步骤:

 

  • 调用方法时, 为对象发送一个 SEL 消息, 如 @selector(personTest)
  • 系统用SEL 与 _mask 作按位与计算: @selector(personTest) & _mask , 假设其值==2,
  • 得到索引2 的bucket_t , 判断其中的 SEL 是否与传过来的 SEL 相同, 如果相同, 这个_imp就是寻找的方法
  • 如果不相同, 索引减1, 再比较SEL, 依次类推. 如果索引<0, 则使索引 = _mask - 1, 直至找到_imp
  • 源码如下:

查看cache_hash的查找方法如下:

6. 为什么按位&_mask?

按位与 可保证得到的值 <= _mask, 这样就不会超出分配的空间.

注: 有的系统是求余 %, 如java, 这样也能保证 <= _mask

7. 为什么有 -1 的算法?

也是因为按位与, 因为不同的值 & _mask, 可能结果相同. 如果已经被占了, 就-1:

8. 如果空间超出原来的_mask, 那怎么办?

刚开始为cache_t分配一定的内存, 系统默认是分配了4, 当内存不够用时, 内存扩大2倍, 依次类推则 _mask *= 2.

每一次_mask 扩容, 散列表清空,为什么要清空呢?因为_mask的值不一样了,如果还保留之前的列表的话,那么与新的_mask &出来的的值就不一样了,所以就清空了。源码如下:

通过源码可以知道如果lodCapacity没有值的话,就取INIT_CACHE_SIZE:

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ONNX Runtime-GPU Linux 是基于 ONNX 运行时 (ONNX Runtime) 的版本,它针对 NVIDIA GPU 进行优化,旨在提供高性能推理加速。ONNX Runtime 是由微软和 Facebook 等公司联合开发的一个开源框架,用于在多种硬件平台上高效运行机器学习模型。 对于 Linux 用户来说,ONNX Runtime-GPU 版本可以显著提高深度学习应用的速度,特别是在对图形处理需求较高的任务上。通过利用 NVIDIA GPU 的并行计算能力,它可以加速模型的预测过程,减少延迟,并提高吞吐量。 要使用 ONNX Runtime-GPU Linux 版本,用户需要安装支持 CUDA 和 cuDNN 的 NVIDIA GPU 驱动程序,并且通常还需要安装适当的库文件如 `libnvinfer` 和 `libnvinfer-dev` 来访问 NVidia TensorRT 推理引擎,这将增强 ONNX Runtime 的性能。此外,系统还需要有 Python 开发环境,因为 ONNX Runtime 支持 Python API,允许开发者轻松集成到现有的 Python 应用中。 以下是使用 ONNX Runtime-GPU 的基本步骤: 1. **安装必要的软件包**:首先安装 CUDA 和 cuDNN 开发工具及库文件,然后安装 ONNX Runtime。具体的命令依赖于您使用的 Linux 发行版。 ```bash # 安装 CUDA and cuDNN sudo apt-get install -y cuda-utils libnvinfer-dev libnvinfer6 # 根据您的 Python 环境安装 ONNX Runtime pip install onnxruntime-gpu ``` 2. **运行示例应用程序**:ONNX Runtime 提供了一些示例应用程序帮助您开始使用,例如图像分类、语音识别等。您可以参考官方文档或 GitHub 存储库中的示例代码来了解如何加载模型并在 GPU 上执行推断。 ```python import onnxruntime # 加载 ONNX 模型 session = onnxruntime.InferenceSession("path/to/model.onnx") # 执行推断 input_name = session.get_inputs().name output_name = session.get_outputs().name result = session.run([output_name], {input_name: your_input_data}) print(result) ``` 3. **性能评估**:为了评估 GPU 的性能提升效果,可以测量在 CPU 和 GPU 上运行相同模型所需的时间。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值