关于Tagged Pointer
在2013年9月,苹果推出了iPhone5s,与此同时,iPhone5s配备了首 个采用64位架构的A7双核处理器,为了节省内存和提高执行效率,苹果提出了Tagged Pointer的概念。先看看原有的对象为什么会浪费内存。假设要存储一个NSNumber对象,其值是一个整数。正常情况下,如果这个整数只是一个NSInteger的普通变量,那么它所占用的内存是与CPU的位数有关,在32位CPU下占4个字节,在64位CPU下是占8个字节的。而指针类型的大小通常也是与CPU位数相关,一个指针所占用的内存在32位CPU下为4个字节,在64位CPU下也是8个字节。所以一个普通的iOS程序,如果没有Tagged Pointer对象,从32位机器迁移到64位机器中后,虽然逻辑没有任何变化,但这种NSNumber、NSDate一类的对象所占用的内存会翻倍。
简述:
-
从64bit开始,iOS引入了Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象的存储
-
在没有使用Tagged Pointer之前, NSNumber等对象需要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值
-
使用Tagged Pointer之后,NSNumber指针里面存储的数据变成了:Tag + Data,也就是将数据直接存储在了指针中
-
当指针不够存储数据时,才会使用动态分配内存的方式来存储数据
-
objc_msgSend能识别Tagged Pointer,比如NSNumber的intValue方法,直接从指针提取数据,节省了以前的调用开销
-
如何判断一个指针是否为Tagged Pointer?
iOS平台,最高有效位是1(第64bit)
Mac平台,最低有效位是1
-
如何进行判断是否是Tagged Pointer?
Tagged Pointer 示例
首先先看NSNumber数值对象
muStr2 = [NSMutableString stringWithString:@"1"];
for(int i=0; i<20; i+=1){
NSNumber *number = @([muStr2 longLongValue]);
NSLog(@"%@, %p", [number class], number);
[muStr2 appendString:@"1"];
}
// 输出结果
__NSCFNumber, 0xb000000000000013
__NSCFNumber, 0xb0000000000000b3
__NSCFNumber, 0xb0000000000006f3
__NSCFNumber, 0xb000000000004573
__NSCFNumber, 0xb00000000002b673
__NSCFNumber, 0xb0000000001b2073
__NSCFNumber, 0xb0000000010f4473
__NSCFNumber, 0xb00000000a98ac73
__NSCFNumber, 0xb000000069f6bc73
__NSCFNumber, 0xb000000423a35c73
__NSCFNumber, 0xb000002964619c73
__NSCFNumber, 0xb000019debd01c73
__NSCFNumber, 0xb000102b36211c73
__NSCFNumber, 0xb000a1b01d4b1c73
__NSCFNumber, 0xb00650e124ef1c73
__NSCFNumber, 0xb03f28cb71571c73
__NSCFNumber, 0xb27797f26d671c73
__NSCFNumber, 0x60000003d540
__NSCFNumber, 0x61000003cb40
__NSCFNumber, 0x61800003c760
数值是1、11、111、1111…..这样递增,可以从输出指针的地址看出最低4位一直为3,这个用于标记是long(float则为4,Int为2,double为5),而最高4位的“b”表示是NSNumber类型;其余56位则用来存储数值本身内容。当存储用的数值超过56位存储上限的时候,那么NSNumber才会用真正的64位内存地址存储数值,然后用指针指向该内存地址。(如果数值长度超过64位,那么就crash)。
因为Tagged Pointed不是一个真正的对象,所以其没有isa。不过只要避免在代码中直接访问对象的isa变量,就没问题。具体如Tagged Pointer 怎么访问类方法列表,之后再详细看下,也许是根据最够为的类型标记,然后调用对应的class方法列表。
再来看看Tagged Pointer String
NSString *str = @"A";
NSString *str2 = [[str mutableCopy] copy];
NSLog(@"str:%p %@", str, str.class);
NSLog(@"str2:%p %@", str2, str2.class);
// 输出结果
str:0x1068a2148 __NSCFConstantString
str2:0xa000000000000411 NSTaggedPointerString
String的TaggedPointer大致和Number一样,最高位表示类型,最低位表示字符串长度,然后字符串内容转为为ASCII码存储(上面的例子A的ASCII为65,转换为16进制是41,而1的ASCII码是49,转换为十六进制则是31)
NSMutableString *muStr2 = [NSMutableString stringWithString:@"1"];
for(int i=0; i<14; i+=1){
NSString *strFor = [[muStr2 mutableCopy] copy];
NSLog(@"%@, %p", [strFor class], strFor);
[muStr2 appendString:@"1"];
}
// 输出结果
NSTaggedPointerString, 0xa000000000000311
NSTaggedPointerString, 0xa000000000031312
NSTaggedPointerString, 0xa000000003131313
NSTaggedPointerString, 0xa000000313131314
NSTaggedPointerString, 0xa000031313131315
NSTaggedPointerString, 0xa003131313131316
NSTaggedPointerString, 0xa313131313131317
NSTaggedPointerString, 0xa0079e79e79e79e8
NSTaggedPointerString, 0xa1e79e79e79e79e9
NSTaggedPointerString, 0xa03def7bdef7bdea
NSTaggedPointerString, 0xa7bdef7bdef7bdeb
__NSCFString, 0x60000003e7c0
__NSCFString, 0x61800003e5a0
__NSCFString, 0x60800003e2c0
从上面的指针输出可以看出,最低位表示字符串的长度,而其余的56位也是用来存储数组,这里需要注意的是,当字符串内存长度超过了56位的时候,Tagged Pointer并没有立即用指针转向,而是用了一种算法编码,把字符串长度进行压缩存储(具体算法我还不太明白),当这个算法压缩的数据长度超过56位了才使用指针指向。
推荐两篇文章:
https://www.jianshu.com/p/e354f9137ba8
http://www.cocoachina.com/ios/20150918/13449.html