本节书摘来自华章出版社《编写高质量代码:改善Objective-C程序的61个建议》一 书中的第2章,作者:刘一道,更多章节内容可以访问云栖社区“华章计算机”公众号查看。
在Mac OS X 10.6(雪豹)中开始支持64位,如今最新版本iPhone 5s也开始采用Arm64架构。在64位化的过程中,其中一个比较关键的改进就是,Mac OS 10.7(美洲虎)和iOS 7的64位环境先后引入了标记(Tagged)指针。下面就简单地来介绍一下标记(Tagged)指针。在介绍标记(Tagged)指针之前有必要介绍一下指针地址对齐概念和64位环境的一些变化。
指针地址对齐在32位环境下,要读取一个32位整数,如果这个32位整数在内存地址为0x00000002-0x00000006(仅作举例,这个地址一般是被系统保留的)的内存上,读取这个整数会消耗2个CPU周期,而如果这个数在0x00000004~0x00000008的内存上只需要一个CPU周期。为了加快内存的CPU访问,程序都使用了指针地址对齐概念。指针地址对齐就是指在分配堆中的内存时往往采用偶数倍或以2为指数倍的内存地址作为地址边界。几乎所有系统架构,包括Mac OS和iOS,都使用了地址对齐概念。 void *a = malloc(1); void *b = malloc(3); NSLog(@"a: %p",a); NSLog(@"b: %p",b);运行这段代码后,得到了如下结果:
a: 0x8c11e20 b: 0x8c11e30可以看到,a和b指针的最后4位都是0,虽然a只占用31个字节,但是a和b的地址却相差16个字节。因为iOS中是以16个字节为内存分配边界的,或者说iOS的指针地址对齐是以16个字节为对齐边界的。进一步说,iOS中分配的内存地址最后4位永远都是0。
64位地址iPhone 5s中采用了Arm64的CPU,同时也支持了64位的App。64位App中指针大小也扩大到64位,就是理论上可以支持最大264字节(达千万T字节)的内存地址空间。而对于大多数应用来说,这么大的地址空间完全是浪费的。也就是说,64位环境下,内存地址的前面很多位一般都是0。标记(Tagged)指针由于指针地址对齐概念和64位超大地址的出现,指针地址仅仅作为内存的地址是比较浪费的,故此,可以在指针地址中保存或附加更多的信息。这就引入了标记(Tagged)指针概念。标记(Tagged)指针是指那些指针中包含特殊属性或信息的指针。其中指针对齐概念可以来标识一个指针是否是标记(Tagged)指针及相关类型,64位的地址指针又可以提供保存额外信息的足够空间。如今,iOS 7的64位环境和Mac OS 10.7(Lion)中开始引入了标记(Tagged)指针。标记(Tagged)指针对NSNumber的优化标记(Tagged)指针一个比较典型的应用就是NSNumber。在64位环境下,对于一般的数字,NSNumber不用再分配内存了。现在,看看NSNumber是如何运用标记(Tagged)指针的: NSNumber *number3 = @3; NSNumber *number4 = @4; NSNumber *number9 = @9; NSLog(@"number3 pointer is %p", number3); NSLog(@"number4 pointer is %p", number4); NSLog(@"number9 pointer is %p", number9);在64位模拟器中运行后,得到了如下结果:
number3 pointer is 0xb000000000000032 number4 pointer is 0xb000000000000042 number9 pointer is 0xb000000000000092可以看出number3、number4和number9的值前4位都是0xb,后4位都是0x2(指针的Tag),中间就是实际的取值。因此,这些NSNumber已经不需要再分配内存(指堆中内存)了,直接可以把实际的值保存到指针中,而无须再去访问堆中的数据。这无疑提高了内存访问速度和整体运算速度。也就是说,标记(Tagged)指针本身就可以表示一个NSNumber了,在64位环境下运行这段代码:
NSLog(@"0xb000000000000052's class is %@",[(NSNumber*)0xb000000000000052 class]);会输出如下结果:
0xb000000000000052's class is __NSCFNumber那么,如果一个数超过了标记(Tagged)指针所能表示的范围,系统会怎么处理?看看这段代码:
NSNumber *numberBig = @(0x1234567890ABCDEF); NSLog(@"numberBig pointer is %p", numberBig);在64位模拟器中运行后,得到了如下结果:
numberBig pointer is 0x1094026a0可以看出numberBig指针最后4位都是0,应该是分配在堆中的对象。因此,如果NSNumber超出了标记(Tagged)指针所能表示的范围,系统会自动采用分配成对象,可以根据指针的最后4位是否为0来区分。
标记(Tagged)指针对isa指针优化查看NSObject类的头文件,你会发现这段定义: @interface NSObject <NSObject> { Class isa; }所有类都继承自NSObject,因此每个对象都有一个isa指针指向它所属的类。在32位和64位的环境下, isa指针会产生不同的变化。在32位环境下,对象的引用计数都保存在一个外部的表中,而对引用计数的增减操作都要先锁定这个表,操作完成后才解锁。这个效率是非常慢的。而在64位环境下,isa也是64位,实际作为指针部分只用到其中的33位,剩余的部分会运用到标记(Tagged)指针的概念。其中19位将保存对象的引用计数,这样对引用计数的操作只需要原子的修改这个指针即可。如果引用计数超出19位,才会将引用计数保存到外部表,而这种情况往往是很少的,因此效率将会大大提高。 要点(1)利用标记(Tagged)指针,可以在指针地址中保存或附加更多的信息。(2)利用标记(Tagged)指针处理NSNumber,直接可以把实际的值保存到指针中,而无须再去访问堆中的数据,可提高内存访问速度和整体运算速度。(3)在32位和64位的环境下,isa指针会产生不同的变化。在64位环境下,标记(Tagged)指针可加快isa指针的处理效率。
相关资源:jd_seckill京东抢茅台插件最新版【京东飞天茅台1499抢购】Python脚本的完整安装 使用教程