理解Tagged Pointer

文章来源于DC蜗牛的博客《从一个例子来看Tagged Pointer特性》

一个错误例子

@property (nonatomic, strong) NSString *string;

dispatch_queue_t queue = dispatch_queue_create("memoryBeingFreedCase_4", DISPATCH_QUEUE_CONCURRENT);

for (int i = 0; i < 1000000; i++) {

dispatch_async(queue, ^{

self.string = [NSString stringWithFormat:@"The num is %d", i];

});

}

当运行后, 就会崩溃在给self.string的一行, 错误log如下:

malloc: *** error for object 0x600000639480: Non-aligned pointer being freed (2)

错误原因

来看下原因是为何? 其实是因为setter方法中, 对strong修饰的属性会有一个retain和release的操作。 在并发多线程中的赋值操作中, 都是对_string指针进行的操作, 可能在_string刚刚被release后进行第3行代码的赋值操作。这时_string指向的内存地址是已经被释放了, 所以造成上面的错误。

- (void)setString:(NSString *)string {

    [string retain];  //1

    [_stirng release];//2

    _string = string; //3

}

解决方案

1.将并发执行的任务改为串行执行。  

2.将属性开启atomic原子特性。  

3.利用Tagged Pointer特性。前两个方案的具体方法就不絮述了, 只说第三个方案。如果将上面的例子中, 改动一行代码, 重新运行。

self.string = [NSString stringWithFormat:@"%d", i];

这时, 你会发现, 竟然没有问题了, 这究竟为什么呢, 到底什么这么神奇呢? 下面直接引入本文主题Tagged Pointer。

Tagged Pointer

2013年9月, 苹果发布iPhone5s, 其搭载了苹果A7处理器, 是首个采用64位架构的处理器。关于iPhone系列的处理器指令集可以参阅之前写过一篇关于Architectures与指令集架构的博客。也是从采用64位处理器后, 为了节省内存和提高执行效率,苹果提出了Tagged Pointer的概念。对于64位程序,引入Tagged Pointer后,相关逻辑能减少一半的内存占用,以及3倍的访问速度提升,100倍的创建、销毁速度提升。

Tagged Pointer 之前

比如, NSInteger类型的变量,它所占用的内存是与处理器的位数有关,在32位CPU下占4个字节,在64位CPU下是占8个字节的。而指针类型的大小通常也是与CPU位数相关,一个指针所占用的内存在32位CPU下为4个字节,在64位CPU下也是8个字节。 

所以在没有Tagged Pointer对象之前,从32位机器迁移到64位机器中后,虽然逻辑没有任何变化,但这种NSNumber、NSDate一类的对象所占用的内存会翻倍。而且从效率上来说,我们需要在堆上为其分配内存,另外还要维护它的引用计数,管理它的生命期。这些都给程序增加了额外的逻辑,造成运行效率上的损失。 

Tagged Pointer 之后

为了改进上面提到的内存占用和效率问题,苹果提出了Tagged Pointer对象。由于NSNumber、NSDate一类的变量本身的值需要占用的内存大小常常不需要8个字节。我们可以将一个对象的指针拆成两部分,一部分直接保存数据,另一部分作为特殊标记,表示这是一个特别的指针,不指向任何一个地址。所以在这总共8字节的内存中, 把标记位除去后, 其他的内存大小都可以存储数据。所以,引入了Tagged Pointer对象之后,其在64位处理器下的内存图变成了以下这样: 


而且, 如果你所要存储的数据大小超出Tagged Pointer对象可存储大小的话, 系统将不会以Tagged Pointer的方式, 将会以普通对象的方式来保存。所以, 这个优化并不需要人为的干预。

代码验证

NSMutableString *string = [NSMutableString stringWithString:@"1"];

for(int i = 0; i < 20; i++){

        NSNumber *number = @([string longLongValue]);

        NSLog(@"%@: %p---%p", [number class], number, &number);

        [string appendString:@"1"];

}

以NSNumber类型举例, 打印结果:

__NSCFNumber: 0xb000000000000013---0x7ffee2f43698

__NSCFNumber: 0xb0000000000000b3---0x7ffee2f43698

__NSCFNumber: 0xb0000000000006f3---0x7ffee2f43698

__NSCFNumber: 0xb000000000004573---0x7ffee2f43698

__NSCFNumber: 0xb00000000002b673---0x7ffee2f43698

__NSCFNumber: 0xb0000000001b2073---0x7ffee2f43698

__NSCFNumber: 0xb0000000010f4473---0x7ffee2f43698

__NSCFNumber: 0xb00000000a98ac73---0x7ffee2f43698

__NSCFNumber: 0xb000000069f6bc73---0x7ffee2f43698

__NSCFNumber: 0xb000000423a35c73---0x7ffee2f43698

__NSCFNumber: 0xb000002964619c73---0x7ffee2f43698

__NSCFNumber: 0xb000019debd01c73---0x7ffee2f43698

__NSCFNumber: 0xb000102b36211c73---0x7ffee2f43698

__NSCFNumber: 0xb000a1b01d4b1c73---0x7ffee2f43698

__NSCFNumber: 0xb00650e124ef1c73---0x7ffee2f43698

__NSCFNumber: 0xb03f28cb71571c73---0x7ffee2f43698

__NSCFNumber: 0xb27797f26d671c73---0x7ffee2f43698

__NSCFNumber: 0x6000006297c0---0x7ffee2f43698

__NSCFNumber: 0x6000006297c0---0x7ffee2f43698

__NSCFNumber: 0x6000006297c0---0x7ffee2f43698

通过结果分析, 在打印地址中除去最后的数字最末尾的3以及最开头的0xb, 其它数字刚好表示了相应NSNumber的值。可见,苹果确实是将值直接存储到了指针本身里面。也可能数字最末尾的3以及最开头的0xb就是苹果对于Tagged Pointer的特殊标记。在最后的三行打印结果中,由于Tagged Pointer无法将其按上面的压缩方式来保存,那么应该就会以普通对象的方式来保存, 所以打印的结果是栈区和堆区的内存地址。

NSMutableString *string2 = [NSMutableString stringWithString:@"1"];

    for( int i = 0; i < 14; i++){

        NSString *strFor = [[string2 mutableCopy] copy];

        NSLog(@"%@: %p---%p", [strFor class], strFor, &strFor);

        [string2 appendString:@"1"];

    }

以NSString类型举例, 打印结果:

NSTaggedPointerString: 0xa000000000000311---0x7ffee9e64698

NSTaggedPointerString: 0xa000000000031312---0x7ffee9e64698

NSTaggedPointerString: 0xa000000003131313---0x7ffee9e64698

NSTaggedPointerString: 0xa000000313131314---0x7ffee9e64698

NSTaggedPointerString: 0xa000031313131315---0x7ffee9e64698

NSTaggedPointerString: 0xa003131313131316---0x7ffee9e64698

NSTaggedPointerString: 0xa313131313131317---0x7ffee9e64698

NSTaggedPointerString: 0xa0079e79e79e79e8---0x7ffee9e64698

NSTaggedPointerString: 0xa1e79e79e79e79e9---0x7ffee9e64698

NSTaggedPointerString: 0xa03def7bdef7bdea---0x7ffee9e64698

NSTaggedPointerString: 0xa7bdef7bdef7bdeb---0x7ffee9e64698

__NSCFString: 0x60400042f560---0x7ffee9e64698

__NSCFString: 0x600000437060---0x7ffee9e64698

__NSCFString: 0x600000436e80---0x7ffee9e64698

这个例子中, 前面的部分类型打印出来都是NSTaggedPointerString, 这就很明显了。也可能开头的0xa就是苹果对于Tagged Pointer的特殊标记。在最后的三行打印结果中,由于Tagged Pointer无法将其按上面的压缩方式来保存,那么应该就会以普通对象的方式来保存, 所有后面的类型也就变为__NSCFString了。

再看下我在控制台进行的一些打印: 

这个打印结果, 足以说明它是一个特别的指针,且不指向任何一个地址。所有对象都有 isa 指针,而Tagged Pointer其实是没有的,因为它不是真正的对象。 所以如果你直接访问Tagged Pointer的isa成员的话,在编译时将会有警告。

特点

1.Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储在堆中,也不需要malloc和free。 

2.在内存读取上有着3倍的效率,创建时比以前快106倍。不但减少了64位机器下程序的内存占用,还提高了运行效率。完美地解决了小内存对象在存储和访问效率上的问题。 

3.这是一个特别的指针,不指向任何一个地址。 

4.Tagged Pointer没有isa指针, 所以其不是真正的对象。

总结

还是得引用唐巧文章中的原话, 苹果将Tagged Pointer引入,给64位系统带来了内存的节省和运行效率的提高。Tagged Pointer通过在其最后一个bit位设置一个特殊标记,用于将数据直接保存在指针本身中。因为Tagged Pointer并不是真正的对象,我们在使用时需要注意不要直接访问其isa变量。

参考文章: 

深入理解Tagged Pointer 

Let’s Build Tagged Pointers 

Tagged Pointer Strings

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

推荐阅读更多精彩内容

  • 前言 在2013年9月,苹果推出了iPhone5s,与此同时,iPhone5s配备了首个采用64位架构的A7双核处...
    woshishui1243阅读 170评论 0 0
  • 在调试程序或者反编译App时,经常可以看到"NSTaggedPointerString"这个东西例如: 打印: 这...
    Mr_Baymax阅读 10,404评论 15 48
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,136评论 30 470
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,094评论 1 32
  • 这篇文章写了一周,断断续续,后面加了一些东西,可能有点接不上。 发完这条微博后,引发了很多的评论,于是今天早上快6...
    阿伦影子阅读 372评论 0 0