美文网首页
C中TaggedPointer

C中TaggedPointer

作者: 来自蒙塔基的钢蛋儿 | 来源:发表于2019-03-15 18:06 被阅读0次

    runtime底层Class创建的时候回调用到isTaggedPointer方法,TaggedPointer这是什么?


    下面介绍一下TaggedPointer

    TaggedPointer是一个能够提升性能, 节省内存的技术. NSString, NSNumber中就采用了这个技术.
    对象在内存中都是对齐的, 它们的地址总是指针大小的整数倍, 通常是16的倍数. 在64位系统中, 对象指针是一个64位的整数, 为了对齐, 对象指针的一些位永远是0.
    举一个例子, 假设要存储一个NSNumber对象,其值是一个整数. 正常情况下, 如果这个整数只是一个NSInteger的普通变量, 那么它所占用的内存是与CPU的位数有关, 在32位CPU下占4个字节, 在64位CPU下是占8个字节的. 而指针类型的大小通常也是与CPU位数相关, 一个指针所占用的内存在32位CPU下为4个字节, 在64位CPU下也是8个字节. 所以一个普通的iOS程序, 如果没有TaggedPointer对象, 从32位机器迁移到64位机器中后, 虽然逻辑没有任何变化, 但这种NSNumber、NSDate一类的对象所占用的内存会翻倍。
    与此同时, 在没有使用TaggedPointer之前, NSNumber等对象需要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值.
    为了改进上面提到的内存占用和效率问题, 苹果提出了TaggedPointer对象. 由于NSNumber, NSDate, NSString等类的变量本身的值需要占用的内存大小常常不需要8个字节, 拿整数来说, 4个字节所能表示的有符号整数就可以达到20多亿(注:2^31=2147483648,另外1位作为符号位), 对于绝大多数情况都是可以处理的.
    在使用TaggedPointer之后, NSNumber指针里面存储的数据变成了: Tag + Data.
    因此可以说使用了TaggedPointer以后, 对象指针是一个伪指针, 不再指向对象在堆上的地址, 也就是将数据直接存储在了指针中, 当指针不够存储数据时, 才会使用动态分配内存的方式来存储数据.
    如何判断一个对象指针是否是TaggedPointer

    我们通过objc判断该对象指针是否是TaggedPointer, 通过查看对象指针的tag bit是否为1来判断, 源码如下:

    //如果是iOS平台, 最高有效位是1(第64bit, 使用高位优先规则 MSB)
    #define _OBJC_TAG_MASK (1UL<<63)
    //如果是mac平台, 最低有效位是1(低位优先规则 LSB)
    #define _OBJC_TAG_MASK 1UL
    
    // objc-object.h 中
    inline bool objc_object::isTaggedPointer() {
        return _objc_isTaggedPointer(this);
    }
    
    // objc-internal.h 中
    static inline bool _objc_isTaggedPointer(const void * _Nullable ptr) {
        return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
    }
    

    结论: 使用对象指针的最高位bit或者最低位bit来标识该指针是否是Tagged Pointer.
    为什么可以通过设定最高位或者最低位是否为 1 来标识呢?
    因为在对象指针变量分配内存的时候, 都是按2的整数倍来分配的, 这样分配出来的正常内存地址末位不可能为1, 这样通过将最低标识为 1, 就可以和其他正常指针做出区分.
    那么为什么最高位为 1, 也可以标识呢?
    这是因为64位操作系统, 设备一般没有那么大的内存, 所以内存地址一般只有 48 个左右有效位, 也就是说高位的 16 位左右都为 0, 所以可以通过最高位标识为 1 来表示TaggedPointer. 那么既然一位就可以标识TaggedPointer了其他的信息是干嘛的呢? 我们可以想象, 要有一些bit位来表示这个指针对应的类型, 不然拿到一个TaggedPointer的时候我们不知道类型, 就无法解析成对应的值。
    从源码中我们可以有如下注释, 我进行了翻译:

    /***********************************************************************
    * Tagged pointer objects. - 对于 TaggedPointer 对象的描述
    *
    * Tagged pointer objects store the class and the object value in the 
    * object pointer; the "pointer" does not actually point to anything.
    * TaggedPointer对象的内容, 是将对象指针(object pointer)用来保存类(class)和对象值(object value)!!! `pointer`指针并没有指向任何对象(obejct), 我们称这个为伪指针!!!
    *
    * Tagged pointer objects currently use this representation:
    * (LSB) 低bit
    *  1 bit   set if tagged, clear if ordinary object pointer
    *  3 bits  tag index  -- 用来表示 object的class
    * 60 bits  payload -- 这个负载是 object的class定义的内容(NSString, NSNumber定义的内容不同)
    * (MSB) 高bit
    * The tag index defines the object's class. 
    * The payload format is defined by the object's class.
    *
    * If the tag index is 0b111, the tagged pointer object uses an 
    * "extended" representation, allowing more classes but with smaller payloads:
    * 如果tagindex的内容是`ob111`, 这个`TaggedPointer`对象使用的是`extended`扩展描述法, 这个伪指针可以支持更多的类, 但是对应的用来描述object值的bits减少成52bits
    * (LSB) -- 低bit
    *  1 bit   set if tagged, clear if ordinary object pointer - 用来展示当前对象指针是taggedPointer还是普通对象指针!!!
    *  3 bits  0b111 -- 描述是否是 extended tagged pointer
    *  8 bits  extended tag index -- 扩展 tag index
    * 52 bits  payload -- 具体的 objct值
    * (MSB) -- 高bit
    *
    * Some architectures reverse the MSB and LSB in these representations.
    *
    * This representation is subject to change. Representation-agnostic SPI is:
    * objc-internal.h for class implementers.
    * objc-gdb.h for debuggers.
    **********************************************************************/
    

    总而言之:

    低地址中最低的1bit, 标志当前对象指针(object pointer)是否是TaggedPointer.
    接着3bit位(转化成10进制是 0~7), 标志当前对象是哪个类的内容, 具体标志的内, 通过下面的枚举能看到, 常用的是OBJC_TAG_NSString和OBJC_TAG_NSNumber, 分别是2和3.
    如果tagged index == 0b111, 标志extended tag index.
    其余的60bit 或者 52bit 就用来存储具体的 object value.(如果tag index对应是NSString, object value 是经过编码的, 参考文章中有具体的编码方法)

    这样, 通过TaggedPointer这种伪指针方法, 一次性将class - objectValue放在对象指针中, 即节省内存, 又提高了效率. 当然如果60bit/52bit还不能满足object value的范围, objc仍然会把它当做普通对象指针处理!!!

    // 具体 tag index 与类关系如下
    {
        // 60-bit payloads
        OBJC_TAG_NSAtom            = 0, 
        OBJC_TAG_1                 = 1, 
        OBJC_TAG_NSString          = 2, 
        OBJC_TAG_NSNumber          = 3, 
        OBJC_TAG_NSIndexPath       = 4, 
        OBJC_TAG_NSManagedObjectID = 5, 
        OBJC_TAG_NSDate            = 6,
    
        // 60-bit reserved
        OBJC_TAG_RESERVED_7        = 7, 
    
        // 52-bit payloads
        OBJC_TAG_Photos_1          = 8,
        OBJC_TAG_Photos_2          = 9,
        OBJC_TAG_Photos_3          = 10,
        OBJC_TAG_Photos_4          = 11,
        OBJC_TAG_XPC_1             = 12,
        OBJC_TAG_XPC_2             = 13,
        OBJC_TAG_XPC_3             = 14,
        OBJC_TAG_XPC_4             = 15,
    
        OBJC_TAG_First60BitPayload = 0, 
        OBJC_TAG_Last60BitPayload  = 6, 
        OBJC_TAG_First52BitPayload = 8, 
        OBJC_TAG_Last52BitPayload  = 263, 
    
        OBJC_TAG_RESERVED_264      = 264
    }
    

    TaggedPointer使用的实例
    可以参考文献, 其中有TaggedPointer在NSNumber和NSString中的实践应用.
    注意NSString到最后使用成为: NSTaggedPointerString 和 __NSCFString

    相关文章

      网友评论

          本文标题:C中TaggedPointer

          本文链接:https://www.haomeiwen.com/subject/oomrmqtx.html