概念
在计算机领域中,tagged pointer 是一个指针(内存地址),这种指针关联着额外的数据信息,例如indirection bit(间接位)或者refrence count(引用技术),这些数据通常被“folded”到指针中。这些额外数据称作为“标签”,“标签”是用来指定类型。
应用
关于Tagged pointer应用在很多地方,这里主要谈论在Objective-C中的应用,如果想要有更多了解,可以参考WikipediA的Tagged pointer这篇文章。下面我们就来谈谈Tagged pointer技术在Objective-C中的应用:
在OS X 10.10中,Apple就开始使用Tagged pointer技术了,OC中的class points(类指针)是字对齐的,也就是说对象在内存中是对齐的,它们的地址总是指针大小的整数倍,通常是16的倍数,OC中对象指针是一个64位整数,如果指针的最低有效位为1(奇数),这个指针就是Tagged pointer,紧接着3位表示类表的索引,剩下的60位为类所用。Tagged pointer中的“标签”用于许多用途,例如:存储引用技术和是否含义析构函数。
Tagged pointer在OC中有一个简单的应用就是NSNumber,接下来我们来看看NSNumber是如何应用Tagged pointer:
下面是我在64位 Mac上Xcode 8.0的运行代码:
for (int i = 0; i < 11; i++) {
NSNumber *num = [NSNumber numberWithInt:i];
// 打印出地址
NSLog(@"%p\n", num);
}
输出如下:
2016-10-30 15:13:36.073573 LearningOC-NSObject[39610:2880360] 0x27
2016-10-30 15:13:36.073605 LearningOC-NSObject[39610:2880360] 0x127
2016-10-30 15:13:36.073620 LearningOC-NSObject[39610:2880360] 0x227
2016-10-30 15:13:36.073722 LearningOC-NSObject[39610:2880360] 0x327
2016-10-30 15:13:36.073739 LearningOC-NSObject[39610:2880360] 0x427
2016-10-30 15:13:36.073755 LearningOC-NSObject[39610:2880360] 0x527
2016-10-30 15:13:36.073769 LearningOC-NSObject[39610:2880360] 0x627
2016-10-30 15:13:36.073782 LearningOC-NSObject[39610:2880360] 0x727
2016-10-30 15:13:36.073797 LearningOC-NSObject[39610:2880360] 0x827
2016-10-30 15:13:36.073812 LearningOC-NSObject[39610:2880360] 0x927
2016-10-30 15:13:36.073850 LearningOC-NSObject[39610:2880360] 0xa27
由输出可以看出num明显不是对象指针,而是Tagged pointer。理由很简单:因为在64位Mac地址空间的开头里,4GB零页是未映射且不可映射的,所以上面输出的地址明显很小,不可能是对象指针。
输出地址中,最后一位7用二进制表示为: 1001
最后一位1表示该指针为 Tagged pointer; 紧跟着三位100表示NSNumber这个类;倒数第二位2用二进制表示为0010,表示数据类型(int),这个也可以从以下实验看出:
NSNumber *floatNum = [NSNumber numberWithFloat:1];
NSLog(@"floatNum:%p\n", floatNum);
NSNumber *intNum = [NSNumber numberWithInt:1];
NSLog(@"intNum:%p\n", intNum);
NSNumber *longNum = [NSNumber numberWithLong:1];
NSLog(@"longNum:%p\n", longNum);
NSNumber *charNum = [NSNumber numberWithChar:1];
NSLog(@"charNum:%p\n", charNum);
输出如下:
2016-10-30 15:42:44.870889 LearningOC-NSObject[41617:2922729] floatNum:0x147
2016-10-30 15:42:44.870912 LearningOC-NSObject[41617:2922729] intNum:0x127
2016-10-30 15:42:44.870931 LearningOC-NSObject[41617:2922729] longNum:0x137
2016-10-30 15:42:44.870951 LearningOC-NSObject[41617:2922729] charNum:0x107
现在,有上述内容可以看出,64位的地址已经花费了8个位用来表示数据类型、类、指针类型了。留下56位来表示数值了。在int数据中,第一位是符号位,所以我们能表达的最大数值为2^55 - 1,当数值超过2^55 - 1则用NSNumber对象指针来存储,以下实验足以证明这个结论:
NSNumber *num1 = [NSNumber numberWithLong:((long)pow(2, 55)) - 1];
NSNumber *num2 = [NSNumber numberWithLong:((long)pow(2, 55))];
NSLog(@"num1:%p numb2:%p\n", num1, num2);
输出如下:
2016-10-30 16:07:24.943873 LearningOC-NSObject[43567:2961854] num1:0x7fffffffffffff37 numb2:0x100200370
接下来我们来看看关于引用计数,现在我已经把Xcode环境配置为MRC,然后执行以下代码:
NSNumber *intNum = [NSNumber numberWithInt:1];
NSLog(@"retainCount:%lu", (unsigned long) intNum.retainCount);
输出如下:
2016-10-30 16:37:28.496776 LearningOC-NSObject[45654:3007376] retainCount:9223372036854775807
可以看出retainCount是一个巨大的数值,也就表示该对象该内存中不被释放,这样也省去了释放内存操作。
以上是apple使用Tagged pointer应用在NSNumber,下面我们来分析一下这样用的好处:
首先,当创建一个NSNumber对象时,使用Tagged pointer可剩下一个真正的对象内存的分配(因为数值是存在指针里,而不是指针所指向的内存里),其次在调用相应方法是,如doubleValue方法时,是直接取指针中高60位表示的数值,这样可以省下一次间接取值的时间。最后,由于引用计数可以是空指令,因为没有内存需要释放。这对于常用的类,性能也是巨大的提升!
总结
Tagged pointer确实是能够提升内存和性能的有趣的技术.当然,NSNumber只是一个简单的应用,吸引Apple官方对Tagged pointer应用花更多时间处理的当然是NSString(NSMutableString)字符串处理了。这里就不多描述了。感兴趣的可以去看看这篇文章:【译】采用Tagged Pointer的字符串
网友评论