IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    64位与Tagged Pointer

    谌启亮 (shenqiliang@xcodev.com)发表于 2013-10-21 16:09:00
    love 0

    在Mac OS X 10.6(Snow Leopard)中开始支持64位,如今最新版本iPhone 5s也开始采用了Arm64架构。在64位化的过程中,其中一个比较关键的改进就是,Mac OS 10.7(Lion)和iOS 7的64位环境先后引入了Tagged Pointer。下面就简单地来介绍一下Tagged Pointer,在介绍Tagged Pointer之前有必要介绍一下指针地址对齐概念和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只占用1个字节,但是a和b的地址却相差16个字节。因为iOS中是以16个字节为内存分配边界的,或者说iOS的指针地址对齐是以16个字节为对齐边界的。进一步说,iOS中分配的内存地址最后4位永远都是0。

    64位地址

    在不久前发布iPhone5s中采用了Arm64的CPU,同时也支持了64位的App。64位App中指针大小也扩大到64位,就是理论上可以支持最大264字节(达千万T字节)的内存地址空间。而对于大多数应用来说,这么大的地址空间完全是浪费的。也就是说64位环境下,内存地址的前面很多位一般都是0。

    Tagged Pointer

    由于指针地址对齐概念和64位超大地址的出现,指针地址仅仅作为内存的地址是比较浪费的,我们可以在指针地址中保存或附加更多的信息。这就引入了Tagged Pointer概念。Tagged Pointer是指那些指针中包含特殊属性或信息的指针。其中指针对齐概念可以让我们来标识一个指针是否是Tagged Pointer以及相关类型,64位的地址指针又为我们提供保存额外信息的足够空间。如今,iOS 7的64位环境和Mac OS 10.7(Lion)中开始引入了Tagged Pointer。

    NSNumber的优化

    Tagged Pointer一个比较典型的应用就是NSNumber,在64位环境下,对于一般的数字,NSNumber不用再分配内存了。我们看看NSNumber是如何运用Tagged Pointer的:

    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 Pointer本身就可以表示一个NSNumber了,在64位环境下运行这段代码:

    NSLog(@"0xb000000000000052's class is %@",[(NSNumber*)0xb000000000000052 class]);
    

    会输出下面结果:

    0xb000000000000052's class is __NSCFNumber
    

    那么如果一个数超过了Tagged Pointer所能表示的范围,系统会怎么处理?看看这段代码:

    NSNumber *numberBig = @(0x1234567890ABCDEF);
    NSLog(@"numberBig pointer is %p", numberBig);
    

    在64位模拟器中运行后,我得到了如下结果:

    numberBig pointer is 0x1094026a0
    

    可以看出numberBig指针最后4位都是0,应该是分配在堆中的对象。因此,如果NSNumber超出了Tagged Pointer所能表示的范围,系统会自动采用分配成对象,可以根据指针的最后4位是否为0来区分。

    isa指针优化

    查看NSObject类的头文件,你会发现这段定义:

    @interface NSObject  {
        Class isa;
    }
    

    所有类都继承自NSObject,因此每个对象都有一个isa1指针指向它所属的类。在《ARM64 and You》文章中指出:

    在32位环境下,对象的引用计数都保存在一个外部的表中,而对引用计数的增减操作都要先锁定这个表,操作完成后才解锁。这个效率是非常慢的。

    而在64位环境下,isa也是64位,实际作为指针部分只用到的其中33位,剩余的部分会运用到Tagged Pointer的概念,其中19位将保存对象的引用计数,这样对引用计数的操作只需要原子的修改这个指针即可,如果引用计数超出19位,才会将引用计数保存到外部表,而这种情况往往是很少的,因此效率将会大大提高。

    参考文献

    • Objective-C对象模型及应用: http://blog.devtang.com/blog/2013/10/15/objective-c-object-model/
    • ARM64 and You:http://www.mikeash.com/pyblog/friday-qa-2013-09-27-arm64-and-you.html

    1. isa是“is a”,比如“Apple is a company”,表示一种从属关系。↩



沪ICP备19023445号-2号
友情链接