三、Tagged Pointer对象

1、原有系统的问题

假设我们要存储一个NSNumber对象,其值是一个整数,正常情况下,如果这个整数只是一个NSInteger的普通变量,那么它所占用的内存与CPU的位数有关,在32位CPU下占4个字节,在64位CPU下是占8个字节的,而指针类型的大小通常通常也与CPU的位数有关,在32位CPU下占4个字节,在64位CPU下是占8个字节。
所以,一个普通的iOS程序,如果没有Tagged Pointer(标记指针)对象,从32位机器迁移到64位机器中后,虽然逻辑上没有任何变化,但这种NSNumber、NSDate一类的对象所占用的内存会翻倍。

再看看效率,为了存储和访问一个NSNumber对象,我们需要在堆上为其分配内存,另外还要维护它的引用计数,管理它的生命周期。这些都给程序增加了额外的逻辑,造成运行效率上的损失。
image.png

2、Tagged Pointer介绍

由于NSNumber、NSDate一类的变量本身的值需要占用的内存大小常常不需要8个字节,拿整数来说,4个字节所能表示的有符号整数就可以达到20多亿(注:2^31 = 2147483648,另外一位作为符号位),对于绝大多数情况都是可以处理的!

所以我们可以将一个对象的指针拆成两部分,一部分直接保存数据,另一部分作为特殊标记,表示这是一个特别的指针,不指向任何一个地址。所以,引入了Tagged Pointer对象之后,64位CPU下NSNumber的内存图变成了下面这样:
image.png
对此我们可以用代码进行验证:
        NSNumber *number1 = @1;
        NSNumber *number2 = @2;
        NSNumber *number3 = @3;
        NSNumber *numberFFFF = @(0xFFFF);
        
        NSLog(@"number1 pointer is %p", number1);
        NSLog(@"number2 pointer is %p", number2);
        NSLog(@"number3 pointer is %p", number3);
        NSLog(@"numberFFFF pointer is %p", numberFFFF);
image.png

我们将NSNumber类型的指针在64位CPU下直接输出,除去末尾的2和开头的0xb其他的数字刚好表示响应NSNumber的值。猜测:末尾的2和最开头0xb就是Tagged Pointer的特殊标记!?
我们继续验证,尝试方一个8字节长的整数到NSNumber实例中,这样的实例,Tagged Pointer无法将其按上面的压缩方式来保存:

        NSNumber *bigNumber = @(0xFFFFFFFFFFFFFFFF);
        NSLog(@"bigNumber pointer is %p", bigNumber);

打印结果:bigNumber pointer is 0x600000029520

Tagged Pointer特点:

  1. Tagged Pointer专门用来存储小的对象,例如NSNumber和NSDate。
  2. Tagged Pointer指针的值不再是地址,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象"皮"的普通变量而已。所以,它的内存并不存储在堆中,也不需要malloc和free!
  3. 在内存读取上有着以前3倍的效率,创建时比以前快106倍!

结论:

  • 当8个字节可以承载用于表示的数值时,系统会以Tagged Pointer的方式生成指针,如果8个字节承载不了时,则又用以前的方式生产普通的指针!!
  • 引入Tagged Pointer,不但减少了64位机器下程序的内存占用,还提高了运行效率,完美地解决了小内存对象在存储和访问效率上的问题!!

3、注意事项和实现细节

3.1 isa指针

Tagged Pointer的引入也带来了问题,即Tagged Pointer并不是真正的对象,而是一个伪对象,所以你如果完全把它当做对象来使用,可能会出问题,比如:所有对象都有isa指针,而Tagged Pointer其实是没有的,因为它不是真正的对象,以为不是真正的对象,所以你如果直接访问Tagged Pointer的isa成员的话,在编译时会有警告:

obj->isa

我们应该尽量避免上述写法,应该换成相应的方法调用,如isKindOfClass和object_getClass。只要避免在代码中直接访问对象的isa就可以了!

3.2 引用计数

对于64位设备,苹果除了引入Tagged Pointer来优化小的对象外,对于普通的对象,其isa指针也进行了优化和调整!
在32位环境下,对象的引用计数都保存在一个外部的表中,每一个对象的Retain操作,实际上包括如下5个步骤:

  1. 获得全局的记录引用计数的hash表
  2. 为了线程安全,给该hash表枷锁
  3. 查找到目标对象的引用计数值
  4. 将该引用计数值加1,写回hash表
  5. 给该hash表解锁

从上面步骤来看,为了保证线程安全,对引用计数的增减操作都要先锁定这个表,这从性能上看是非常差的!
而在64位环境下,isa指针也是64位的,实际作为指针部分只用到了33位,剩余31位苹果使用了类似Tagged Pointer的概念,其中19位将保存对象的引用计数,这样对引用计数的操作只需要修改这个指针即可。只有当引用计数超出19位,才会将引用计数保存到外部表,而这种情况是很少,所以这样引用计数的更改销量会更高!
在64位环境下,新的retain操作包括如下5个步骤:

  1. 检查isa指针上面的标记位,看引用计数是否保存在isa变量中,如果不是,则使用以前的步骤,否则执行第2步
  2. 检查当前对象是否正在释放,如果是,则不做任何事情
  3. 增加该对象的引用计数,但是并不是马上写回到isa变量中
  4. 检查增加后的引用计数的值是否能够被19位表示,如果不是,则切换成以前的办法,否则执行第5步
  5. 进行一个原子的写操作,将isa的值写回

由于没有了全局的加锁操作,所以引用计数的更改更快了!

3.3 isa的bit位

image.png
union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    Class cls;
    uintptr_t bits;
#if SUPPORT_NONPOINTER_ISA
# if __arm64__
#   define ISA_MASK        0x00000001fffffff8ULL
#   define ISA_MAGIC_MASK  0x000003fe00000001ULL
#   define ISA_MAGIC_VALUE 0x000001a400000001ULL
    struct {
        uintptr_t indexed           : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 30; // MACH_VM_MAX_ADDRESS 0x1a0000000
        uintptr_t magic             : 9;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 19;
#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)
    };
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x0000000000000001ULL
#   define ISA_MAGIC_VALUE 0x0000000000000001ULL
    struct {
        uintptr_t indexed           : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 14;
#       define RC_ONE   (1ULL<<50)
#       define RC_HALF  (1ULL<<13)
    };
# else
    // Available bits in isa field are architecture-specific.
#   error unknown architecture
# endif
// SUPPORT_NONPOINTER_ISA
#endif
};

SUPPORT_NONPOINTER_ISA 用于标记是否支持优化的 isa 指针,其字面含义意思是 isa 的内容不再是类的指针了,而是包含了更多信息,比如引用计数,析构状态,被其他 weak 变量引用情况。判断方法也是根据设备类型:

#if !__LP64__  ||  TARGET_OS_WIN32  ||  TARGET_IPHONE_SIMULATOR  ||  __x86_64__
#   define SUPPORT_NONPOINTER_ISA 0
#else
#   define SUPPORT_NONPOINTER_ISA 1
#endif

我们可以看到,模拟器也是不支持Tagged Pointer的!
参考:isa 指针 和 IMP 指针

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

推荐阅读更多精彩内容