iOS管理对象内存的数据结构以及操作算法--SideTables、RefcountMap、weak_table_t-二

    这篇文章是之前那篇文章iOS管理对象内存的数据结构以及操作算法--SideTables、RefcountMap、weak_table_t的补充和延伸。如果没有阅读过前一篇文章请先看那一篇。
    上一篇文章中关于SideTables、SideTable和RefcountMap三者关系可能描述的不太清楚。很多朋友表示看起来晕乎乎的。当初我在研究的时候也是蒙圈了好长一段时间。所以特意写了这篇文章来补充说明一下,同时也有新的知识扩展。
    刚开始写文章,思路不太清晰。如果文章中有什么错误或者问题,欢迎大家指正。
    这里特别感谢大墙66370午夜时分挑灯夜战提出的宝贵问题。

本文流程
一、解释分离锁是什么。
二、举例阐述SideTables、SideTable、RefcountMap三者关系。
三、第一篇文章所说的N路并发是什么意思。
四、SideTables所使用的Hash算法解密。
五、RefcountMap是什么结构。

一、分离锁

    分离锁并不是一种锁,而是一种对锁的用法。(下面继续上一张感人的手绘图。哈哈我写的字也出现了。如果比写字丑,一般很少有人能比我写的丑)

6DA5F9AD-73C5-4888-9E03-1C72D0E848F1.png

    图1这样对一整个表加一把锁,是我们平时比较常见的。如果我一次写操作需要操作表中多个单元格的数据,比如第一次操作0、1、2位置的数据,第二次操作0、2、3位置的数据。像这种情况锁的粒度就是以整张表为单位的,才能保证数据的安全。
    图2这样对表中的各个元素分别加一把锁就是我们说的分离锁。适用于表中元素相互独立,你对第一个元素做写操作的时候不需要影响到其他元素。
    上文中所说的结构就是SideTables这个大的Hash表中每一个小单元格(SideTable)都带有一把锁。做写操作的时候(操作对象引用计数)单元格之间相互独立,互相没影响。所以降低了锁的粒度。

对比一下图1和图2的并发性。

  • 图1因为任何操作都需要锁整张表,所以写操作的时候相当于串行操作。没有并发。
  • 图2因为每一个单元格都有一把锁,所以写操作的时候有多少个单元格并发数就可以是多少。

这里注意区分一下并发和并行的区别

当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状态.这种方式我们称之为并发(Concurrent).
当系统有一个以上CPU时,则线程的操作有可能非并发.当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)

    可以看到在单元格之间相互独立的情况下图2的方法效率更高。
    看了上面的例子有同学会有疑问。既然分离锁可以实现高并发,那么为什么不对每一个内存对象加一把锁呢?为什么还会有Hash表还会冲突呢?这个问题我在下面通过一个例子和RefcountMap一起解释。

二、为什么SideTables会冲突、SideTable又扮演着什么角色、RefcountMap是用来干嘛的?

    下面我用一个不太恰当的例子来说明问题
假设有80个学生需要咱们安排住宿,同时还要保证学生们的财产安全。应该怎么安排?
    显然不会给80个学生分别安排80间宿舍,然后给每个宿舍的大门上加一把锁。那样太浪费资源了锁也挺贵的,太多的宿舍维护起来也很费力气。
    我们一般的做法是把80个学生分配到10间宿舍里,每个宿舍住8个人。假设宿舍号分别是101、102 、... 110。然后再给他们分配床位,01号床、02号床等。然后给每个宿舍配一把锁来保护宿舍内同学的财产安全。为什么不只给整个宿舍楼上一把锁,每次有人进出的时候都把整个宿舍楼锁上?显然这样会造成宿舍楼大门口阻塞。
    OK假如现在有人要找102号宿舍的2号床的人聊天。这个人会怎么做?

  • 1、找到宿舍楼(SideTables)的宿管,跟他说自己要找10202(内存地址当做key)。
  • 2、宿管带着他SideTables[10202]找到了102宿舍SideTable,然后把102的门一锁lock,在他访问102期间不再允许其他访客访问102了。(这样只是阻塞了102的8个兄弟的访问,而不会影响整栋宿舍楼的访问)
  • 3、然后在宿舍里大喊一声:"2号床的兄弟在哪里?"table.refcnts.find(02)你就可以找到2号床的兄弟了。
  • 4、等这个访客离开的时候会把房门的锁打开unlock,这样其他需要访问102的人就可以继续进来访问了。
SideTables == 宿舍楼
SideTable  == 宿舍
RefcountMap里存放着具体的床位

    苹果之所以需要创造SideTables的Hash冲突是为了把对象放到宿舍里管理,把锁的粒度缩小到一个宿舍SideTable。RefcountMap的工作是在找到宿舍以后帮助大家找到正确的床位的兄弟。

三、N路的并发写操作那句话是什么意思?

上一篇文章中提到:
因为是使用对象的内存地址当key所以Hash的分部也很平均。假设Hash表有n个元素,则可以将Hash的冲突减少到n分之一,支持n路的并发写操作。

    我们在分配宿舍的时候是给同学分配宿舍和床位,然后再给宿舍和床位编号。所以我们可以很平均的给每个宿舍分配8个人。
    那么如果我们用对象内存地址当做Hash算法的key,所得到的散列值可能会出现某些宿舍分配了4个人,某些宿舍分配了12个人的情况。这样人员分配就不平均了。如果某一时间段正好这个宿舍的12个人的访问量都特别大,那么访问起来就又会出现阻塞了。而那4人间的宿舍就会闲置,造成了资源的浪费。会不会造成这种资源浪费主要看两点。

  • 1、我们的key值分部是否平均。

  • 2、我们采用的散列算法能不能尽量把输出值平均分配。

    1、在数据量足够大的情况下我们的key值分部是平均的。因为key值是内存地址。从低位0x0000...0000到高位0xffff...ffff分配。并且操作系统的内存管理模块本身也会对内存分配做很多优化。毕业年头长了,内管管理具体的细节我也扯不出来了。赶紧贴一片文章压压惊,有兴趣的同学可以看操作系统内存管理——分区、页式、段式管理
    2、那么SideTables使用的Hash算法是什么呢?我们来开一个新的大标题。

四、SideTables所使用的Hash算法解密。

    SideTables的定义:NSObject.mm line 207-209

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

    如果看不懂没关系,当它是一个C++的Map。咱们来看StripedMap的定义:objc-private.h line 867-906
其中有用的部分是这样的

...
//如果是嵌入式系统StripeCount=8。我们这里StripeCount=64
enum { StripeCount = 64 };
...
static unsigned int indexForPointer(const void *p) {
    //这里是类型转换,不用在意
    uintptr_t addr = reinterpret_cast<uintptr_t>(p);

    //这里就是我们要找的Hash算法了
    return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
}
  • 1、将对象的内存地址addr右移4位得到结果1

  • 2、将对象的内存地址addr右移9位得到结果2

  • 3、将结果1和结果2做按位异或得到结果3

  • 4、将结果3和StripeCount做模运算得到真正的Hash值。

    因为最后模运算的结果范围是在0-63之间,可见SideTables一共有64个单元格。

五、RefcountMap是什么结构。

    前面文章中提到过if(引用计数器 != table.refcnts.end())因此有同学提问end()是什么?那么咱们得研究一下RefcountMap是什么类型的
    看定义NSObject.mm line 137

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

    看起来好复杂,先不管它。一路顺着定义找下去。找到DenseMap ==> DenseMapBase ==> inline iterator end()发现咱们当前在llvm-DenseMap.h line 77-79。艾玛吓死我了怎么看到llvm了。llvm我也不懂,所以还是不要扯的太远。赶紧打开llvm的相关文档看看DenseMapBase中的公开方法有

09719F81-A6B1-4B4E-A906-DAD8F260B052.png

    当然还有更多其他方法,我只是截取了一部分。通过这部分我们可以看出来我们操作的refcnts.find()和refcnts.end()其实都是对一个C++迭代器iterator的操作。而end()的状态表示的是从头开始查找,一直找到最后都没有找到。当前指针指向的是结束位,而不是最后一个元素。
所以 if(引用计数器 == table.refcnts.end())表示查找到最后都没找到if(引用计数器 != table.refcnts.end())表示中途找到了。

    讲到这里想讲的内容已经讲完啦,欢迎评论、点赞、转发。
    欢迎加我的微博http://weibo.com/xuyang186
    ** 转载请注明出处,谢谢。**

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

推荐阅读更多精彩内容