tagged pointer

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/WangErice/article/details/91048938

在项目开发中,我们总是希望能够节约空间,减少不必要的系统开销,或者提升效率,缩短执行时间,当然有时候也会通过增加空间开销来换取执行时间的缩减。指针与CPU位数有关,在32位CPU上占据4个字节,在64位CPU上占据8个字节。通常情况下,存储数据时会先开辟空间存储数据指针,然后再开辟另外的空间去存储真正的对象,同时将对象指针指向该内存空间。而事实上,有些基本的数据类型,在大多数情况下占用的内存空间很小,甚至连对象指针的空间都用不完,比如OC中的NSNumber, NSNumber,int,NSIndexPath等。如果这些数据类型也按照上述的思路去存储,将会带来存储空间的极大浪费。那么我们设想这样一个极端的场景,某个功能实现全局使用了上述的数据类型,在不改变代码实现逻辑的情况下,假设从在32位机器上移植到64位机器上去运行,由于CPU位数的关系,对象所占据的内存空间会需要增加一倍。这其实是没有必要的,尤其是在一个需要创建非常多对象的程序上,对内存的消耗是非常致命的。

所以为了改进上面提到的内存占用和效率问题,apple在iOS的64位处理器上提出了一种提升性能的解决方案-------tagged pointer.简单来说,对于某些占用内存很小的数据实例,不再单独开辟空间去存储,而是将实际的实例值存储在对象的指针中,同时对该指针进行标记,用于区分正常的寻址指针。此时的指针就变成了tag+data的伪指针,同时包含了标记和真实数据而不再作为真实地址进行寻址使用。

我们在objc-750里找到了如下的说明(objc-runtime-new.mm  6694-6723),

从apple给出的声明中,我们可以得到:

(1) 标签指针对象存储了类信息和对象实际的值,此时的指针不指向任何东西;

(2) 使用最低位作为标记位,如果是标签指针对象就标记为1,如果是普通对象类型就标记为0;

(3) 紧接着三位是标签索引位;

(4) 剩余的60位位有效的负载位,标签索引位定义了标签对象代表的对象的真实类型,负载的格式由实际的类定义;

(5) 如果标签位是0b111,表示该对象使用了是被扩展的标签对象,这种扩展的方式可以运训更多的类使用标签对象来表示,同时负载的有效位数变小。这时:

5.1 最低位是标记位;

5.2 紧接着三位位0b111;

5.3 紧接着八位位扩展的标记位;

5.4 剩余的52位才是真正的有效的负载位。

(6) 在有些指令集框架中,使用了反转高低MSB和LBS来表示标签指针,也就是说并不是所有的架构中都使用低位做标记位,在有些架构中是使用高位来作为标记位的。比如iOS就是使用高位作为标志位,而在接下来的讨论中,我们仅以iOS使用高位作为标记位的实现策略来讨论标签指针。

在iOS中,使用最高位作为标签指针标志位,接下来的三位来作为类标记位,且当这三位为111的时候标签指针就变成了扩展的标签指针,所以普通的标签指针最多能表示7(000-110)种对象类型,那这些对象类型都是那些呢(objc4/runtime/objc-internal.h 240-269)?

从上边的定义中,我们可以看到最开头的七种枚举就是最原始的标签指针对象所能代表的六个类原型。而我们比较常见的就是2(NSString), 3(NSNumber), 4(NSIndexPath)和6(NSDate)这四种类型,我们简单使用

NSNumber来验证一下实际的实现过程。

准备工作

在此之前我先来看一下tagged pointer的初始化过程(objc-interal.h 388-412):

在上述的实现中,标签指针对真实的结果进行了一次encode操作,将实际结果与objc_debug_taggedpointer_obfuscator进行异或操作得到(objc-internal.h 375-380),

objc_debug_taggedpointer_obfuscator在10_14之前是0,之后是一个无符号长整型的随机数 (objc-runtime-new.mm 6835-6849),

所以,如果想要得到真实的标签指针的结果,需要对实际的指针值进行解码(也就是与该随机数objc_debug_taggedpointer_obfuscator再进行一个异或运算),但是这个随机在外部并不可见,不过我们知道它一定存在,所以我们可以使用extern关键字引用这一结果,同时自己实现decode操作。

开始验证。验证过程中,需要注意使用真机或者模拟器运行代码,不要使用Command Tool.

分别定义char,short, int, long,float, double基本类型,然后转化为NSNumber对象,decode之后查看对应tagged pointer实际值:

decode之后的对应值如下:

多次运行之后结果不会发生变化。最高四位0xb=1011,其中最高位为1,表明该指针并不是真正的对象指针,而是一个标签指针;紧接着三位是3(011),对应的value的实际类型为NSNumber类型,中间52位为实际负载值,而最后四位则更加具体地表明了实际类型:0表示char类型,1表示short类型,2表示整形,3表示长整型,4表示单精度类型,5表示双精度类型.

而需要注意的是,之所以采用了标签指针是因为对象指针可以完整表示标签+实际值组合,如果这个组合的实际值超出了对象指针可以表示的范围,那么还是要使用真正的对象指针来表示实际的对象空间。例如在64位iOS操作系统上,我们设置一个NSNumber对象的大小,让这个实际值超过52位byte能表示的范围,这个指针就会变成真正的对象指针。

至于其他的标签指针类型,有兴趣的可以自行验证一下。

猜你喜欢

转载自blog.csdn.net/WangErice/article/details/91048938
今日推荐