【iOS】——SideTable

SideTable

Side Table主要用于存储和管理对象的额外信息,特别是与弱引用相关的数据。Side Table的设计和使用是Objective-C运行时实现弱引用的基础,使得ARC(Automatic Reference Counting)能够正确地处理弱引用的生命周期。

新版本的 objc 中引入了 Tagged Pointer,且 isa 采用 union 的方式进行构造,其中 isa 的结构体中有一个 extra_rchas_sidetable_rc,这两者共同记录引用计数器。

在进行retain操作时,会调用rootRetain方法

objc_object::rootRetain() 方法,只看 extra_rc 超出之后 sidetable 相关的代码,删减之后如下:


uintptr_t carry;newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++if (carry) {// Leave half of the retain counts inline and prepare to copy the other half to the side table.transcribeToSideTable = true;newisa.extra_rc = RC_HALF;newisa.has_sidetable_rc = true;
}
if (slowpath(transcribeToSideTable)) {// Copy the other half of the retain counts to the side table.sidetable_addExtraRC_nolock(RC_HALF);
}

这里主要就是调用了 sidetable_addExtraRC_nolock()

bool 
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{assert(isa.nonpointer);// 取出this对象所在的SideTableSideTable& table = SideTables()[this];// 取出SideTable中存储的refcnts,类型为Mapsize_t& refcntStorage = table.refcnts[this];// 记录原始的引用计数器size_t oldRefcnt = refcntStorage;// 容错处理assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;uintptr_t carry;size_t newRefcnt = addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);if (carry) {// SideTable溢出处理refcntStorage =SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);return true;} else {// SideTable未溢出refcntStorage = newRefcnt;return false;}
}
  1. 首先根据 this,也就是对象的地址从 SideTables 中取出一个 SideTable;
  2. 接着获取 SideTable 的 refcnts,这个成员变量是一个 Map;
  3. 然后存储旧的引用计数器;
  4. 然后进行 add 计算,并记录是否有溢出;
  5. 最后根据是否溢出计算并记录结果,最后返回;

SideTables定义

static StripedMap<SideTable>& SideTables() {return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}

SideTablesde的实质类型是存储SideTable的StripedMap。在StripedMap类中有StripeCount定义存储sidetable的最大数量,所以每个SideTablesdes可以对应多个对象,而每个对象对应一个sidetable

img

SideTableBuf

// We cannot use a C++ static initializer to initialize SideTables because
// libc calls us before our C++ initializers run. We also don't want a global 
// pointer to this struct because of the extra indirection.
// Do it the hard way.alignas(StripedMap<SideTable>) static uint8_t SideTableBuf[sizeof(StripedMap<SideTable>)];
  • SideTables 在 C++ 的 initializers 函数之前被调用,所以不能使用 C++ 初始化函数来初始化 SideTables,而 SideTables 本质就是 SideTableBuf;

  • 不能使用全局指针来指向这个结构体,因为涉及到重定向问题;

SideTableBuf 本质上是一个长度为sizeof(StripedMap) 的 char 类型的数组;同时也可以这么理解:

SideTableBuf 本质上就是一个大小为和 StripedMap<SideTable> 对象一致的内存块;

这也是为什么 SideTableBuf 可以用来表示 StripedMap<SideTable> 对象。本质上而言,SideTableBuf 就是指一个 StripedMap<SideTable>对象;

StripedMap < SideTable >

StripedMap 是一个模板类,在这个类中有一个 array 成员,用来存储 PaddedT 对象,并且其中对于 [] 符的重载定义中,会返回这个 PaddedT 的 value 成员,这个 value 就是我们传入的 T 泛型成员,也就是 SideTable 对象。在 array 的下标中,这里使用了 indexForPointer 方法通过位运算计算下标,实现了静态的 Hash Table。而在 weak_table 中,其成员 weak_entry 会将传入对象的地址加以封装起来,并且其中也有访问全局弱引用表的入口。

template<typename T>
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATORenum { StripeCount = 8 };
#elseenum { StripeCount = 64 };
#endifstruct PaddedT {T value alignas(CacheLineSize);};PaddedT array[StripeCount];static unsigned int indexForPointer(const void *p) {uintptr_t addr = reinterpret_cast<uintptr_t>(p);return ((addr >> 4) ^ (addr >> 9)) % StripeCount;}public:T& operator[] (const void *p) { return array[indexForPointer(p)].value; }const T& operator[] (const void *p) const { return const_cast<StripedMap<T>>(this)[p]; }...省略了对象方法...
}
  • 首先根据是否为 iphone 定义了一个 StripeCount,iphone 下为 8;

  • 源码中 CacheLineSize 为 64,使用 T 定义了一个结构体,而 T 就是 SideTable 类型;

  • 生成了一个长度为 8 类型为 SideTable 的数组;

  • indexForPointer() 逻辑为根据传入的指针,经过一定的算法,计算出一个存储该指针的位置,因为使用了取模运算,所以值的范围是 0 ~ (StripeCount-1),所以不会出现数组越界;

  • 后面的 operator 表示重写了运算符 [] 的逻辑,调用了 indexForPointer() 方法,这样使用起来更像一个数组;

SideTables 可以理解成一个类型为 StripedMap< SideTable> 静态全局对象,内部以数组的形式存储了 StripeCount 个 SideTable;

SideTable
struct SideTable {
// 保证原子操作的自旋锁    spinlock_t slock;
// 引用计数的 hash 表RefcountMap refcnts;
// weak 引用全局 hash 表weak_table_t weak_table;//构造函数SideTable() {memset(&weak_table, 0, sizeof(weak_table));}//析构函数~SideTable() {_objc_fatal("Do not delete SideTable.");}...省略对象方法...
}

slock是一个自旋锁,就是为了保证多线程访问安全性

refcnts本质是一个存储对象引用计数的hash表,key为对象,value为引用计数(优化过得isa中,引用计数主要存储在isa中)

weak_table是存储对象弱引用的一个结构体,该结构体内成员如下:

/**全局的弱引用表, 保存object作为key, weak_entry_t作为value* The global weak references table. Stores object ids as keys,* and weak_entry_t structs as their values.*/
struct weak_table_t {// 保存了所有指向特地对象的 weak指针集合weak_entry_t *weak_entries;// weak_table_t中有多少个weak_entry_tsize_t    num_entries;// weak_entry_t数组的countuintptr_t mask;// hash key 最大偏移值,// 采用了开放定制法解决hash冲突,超过max_hash_displacement说明weak_table_t中不存在要找的weak_entry_tuintptr_t max_hash_displacement;
};

下面是refcnts的定义:

typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,RefcountMapValuePurgeable> RefcountMap;

DenseMap 是一个 hash Map,基类 DenseMapBase 中的部分代码,如下,DenseMapBase中重写了操作符 []:

ValueT &operator[](const KeyT &Key) {return FindAndConstruct(Key).second;}

通过传入的 Key 寻找对应的 Value。而 Key 是 DisguisedPtr<objc_object> 类型,Value 是 size_t 类型。即使用 obj.address :refCount 的形式来记录引用计数器;

回到最初的 sidetable_addExtraRC_nolock 方法中:

size_t& refcntStorage = table.refcnts[this];

通过 this ,即 object 对象的地址,取出 refcnts 这个哈希表中存储的引用计数器;

refcnts 可以理解成一个 Map,使用 address:refcount 的形式存储了很多个对象的引用计数器;

总结

img

  • iphone 中 SideTables() 本质是返回一个 SideTableBuf 对象,该对象存储 8 个 SideTable;

  • 因为涉及到多线程和效率的问题,必定不可能只使用一个 SideTable 来存储对象相关的引用计数器和弱引用;

  • Apple 通过对 object 的地址进行运算之后,对 SideTable 的个数进行取模运算,以此来决定将对象分配到哪个 SideTable 进行信息存储,因为有取模运算,不会出现数组溢出的情况;

  • 当对象需要使用到 sideTable 时,会被分配到 8/64 个全局 sideTables 中的某一个表中存储相关的引用计数器或者弱引用信息;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/47754.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【系统架构设计 每日一问】四 如何对关系型数据库及NoSql数据库选型

根据不同的业务需求和场景&#xff0c;选择适合的数据库类型至关重要。以下是一个优化后的表格展示&#xff0c;涵盖了管理型系统、大流量系统、日志型系统、搜索型系统、事务型系统、离线计算和实时计算七大类业务系统的数据库选型建议。先明确下NoSQL的分类 NoSQL数据库分类…

预训练语言模型实践笔记

Roberta output_hidden_statesTrue和last_hidden_states和pooler_output 在使用像BERT或RoBERTa这样的transformer模型时&#xff0c;output_hidden_states和last_hidden_state是两个不同的概念。 output_hidden_states: 这是一个布尔值&#xff0c;决定了模型是否应该返回所…

大数据学习之sparkstreaming

SparkStreaming idea中初步实现 Spark core: SparkContext 核心数据结构&#xff1a;RDD Spark sql: SparkSession 核心数据结构&#xff1a;DataFrame Spark streaming: StreamingContext 核心数据结构&#xff1a;DStream(底层封装了RDD)&#xff0c;遍历出其中的RDD即可进行…

ReadAgent,一款具有要点记忆的人工智能阅读代理

人工智能咨询培训老师叶梓 转载标明出处 现有的大模型&#xff08;LLMs&#xff09;在处理长文本时受限于固定的最大上下文长度&#xff0c;并且当输入文本越来越长时&#xff0c;性能往往会下降&#xff0c;即使在没有超出明确上下文窗口的情况下&#xff0c;LLMs 的性能也会随…

中文之美:荷·雅称

文章目录 引言I 荷雅称水宫仙子、六月花神水芝、水芸溪客、水旦芙蕖、菡萏朱华、红蕖风荷、静客II 与荷、莲相关的句子、诗词周敦颐李商隐李重元杨公远孟浩然刘光祖苏轼汪曾祺席慕蓉余光中引言 中文之美,美在诗词歌赋,美在绝句华章,也美在对事物名称的雅致表达。 中文对万物…

Speaker Tracking SOTA 文章翻译

AV-A-PF Abstract 在室内环境中跟踪多个移动说话者的问题受到了广泛关注。早期的技术完全基于单一模态&#xff0c;例如视觉。最近&#xff0c;多模态信息的融合已被证明在提高跟踪性能以及在像遮挡这样的具有挑战性情况下的鲁棒性方面发挥了重要作用&#xff08;由于摄像机视…

寄存器与CPU和硬盘的关系

寄存器与 CPU 之间的信息传递主要通过数据总线、地址总线和控制总线来实现&#xff1a; 1. 数据总线&#xff08;Data Bus&#xff09;&#xff1a;用于在 CPU 和寄存器之间传输数据。当 CPU 需要从寄存器中读取数据时&#xff0c;数据通过数据总线从寄存器传输到 CPU&#xff…

GPT-4o mini是什么?

今天&#xff0c;全网都知道 OpenAI 发现货了&#xff01; GPT-4o mini 取代 GPT 3.5&#xff0c;从此坐上正主之位。 从官网信息来看&#xff0c;OpenAI 最新推出的 GPT-4o mini 重新定义了 AI 成本效益的标准&#xff0c;其性能优于前代模型 GPT-3.5 Turbo&#xff0c;且成本…

ruoyi-cloud-plus

1.X项目初始化 (dromara.org)参考文档&#xff01; 可以直接参考以上链接&#xff01;我只是整理我自己需要的部分&#xff0c;方便查看使用。 nacos 服务启动顺序 必须启动基础建设: mysql redis nacos可选启动基础建设: minio(影响文件上传) seata(影响分布式事务 默认开启…

弹性伸缩:如何在Eureka中实现服务的自动扩展和收缩

弹性伸缩&#xff1a;如何在Eureka中实现服务的自动扩展和收缩 在微服务架构中&#xff0c;服务的自动扩展和收缩是实现高可用性和成本效益的关键策略。Eureka&#xff0c;作为Netflix开源的服务发现框架&#xff0c;虽然本身不直接提供自动扩展和收缩的功能&#xff0c;但我们…

[2024-7-22]面试题2

Redis的持久化机制,在真实的线上环境中需要采取什么样的策略 在真实的线上环境中&#xff0c;Redis的持久化机制主要有两种&#xff1a;RDB&#xff08;Redis DataBase&#xff09;和AOF&#xff08;Append Only File&#xff09;。每种机制都有其优点和适用场景&#xff0c;实…

Synopsys:Design Compiler的XG模式和DB模式

相关阅读 Synopsyshttps://blog.csdn.net/weixin_45791458/category_12738116.html?spm1001.2014.3001.5482 很久之前&#xff0c;Design Compiler使用的是DB模式&#xff08;包括一些其他工具&#xff0c;例如DFT Compiler, Physical Compiler和Power Compiler&#xff09;&…

二叉树基础及实现(一)

目录&#xff1a; 一. 树的基本概念 二. 二叉树概念及特性 三. 二叉树的基本操作 一. 树的基本概念&#xff1a; 1 概念 &#xff1a; 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0 &#xff09;个有限结点组成一个具有层次关系的集合。 把它叫做树是因…

数据结构之初始二叉树(4)

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a;数据结构&#xff08;Java版&#xff09; 二叉树的基本操作 二叉树的相关刷题&#xff08;上&#xff09;通过上篇文章的学习&#xff0c;我们…

queue的模拟实现【C++】

文章目录 全部的实现代码放在了文章末尾什么是适配器模式&#xff1f;准备工作包含头文件定义命名空间类的成员变量 默认成员函数emptysizefrontbackpushpop全部代码 全部的实现代码放在了文章末尾 queue的模拟实现和stack一样&#xff0c;采用了C适配器模式 queue的适配器一…

Leetcode 3229. Minimum Operations to Make Array Equal to Target

Leetcode 3229. Minimum Operations to Make Array Equal to Target 1. 解题思路2. 代码实现 题目链接&#xff1a;3229. Minimum Operations to Make Array Equal to Target 1. 解题思路 这一题其实也还蛮简单的&#xff0c;我们只需要考察一下两个数组的差值序列即可。 我…

PCI总线域与处理器域

PCI总线域 在x86处理器系统中&#xff0c;PCI总线域是外部设备域的重要组成部分。实际上在Intel的x86处理器系统中&#xff0c;所有的外部设备都使用PCI总线管理。而AMD的x86处理器系统中还存在一条HT(HyperTransport)总线&#xff0c;在AMD的x86处理器系统中还存在HT总线域。…

Java生成四位纯数字并且确保唯一性

背景&#xff1a; 给了我一个需求&#xff0c;由于某些问题原因&#xff0c;需要给属性和数据添加一个code字段&#xff0c;这是给我发的消息 这两个要求其实是同一个需求&#xff0c;就是在创建对象的时候塞入一个unique的code嘛&#xff0c;听起来很简单吧&#xff0c;但是实…

GooglePlay 金融品类政策更新(7月17号)

距离上次政策大更新&#xff08;4月5号&#xff09;才过去了3个月&#xff0c;Google Play又迎来了一次大更新&#xff0c;不得不说Google Play的要求越来越高了。 我们来梳理一下这次GooglePlay针对金融品类更新了哪些政策: 1.要求提供金融产品和服务的开发者必须注册为组织…

Window环境下MySQL管理

1、MySQL服务启用和停止 图形化界面管理 使用键盘组合键&#xff08;Win R&#xff09;打开运行对话框&#xff0c;在对话框中输入services.msc并点击确定。 这里可以看到服务名称为MySQL84并处于正在运行的状态。 选中后右键可以进行暂停、停止、重启等操作。 命令提示符管理…