iOS性能优化(内存分布与TaggedPointer)

作者: 聪莞 | 来源:发表于2019-04-10 10:11 被阅读9次

    iOS中的五大内存区域

    iOS中的内存区域从低地址到高地址分别为 .text段(代码区)、.data段(已初始化的全局变量、静态变量)、.bss段(未初始化的全局变量、静态变量)、堆区、栈区。


    image.png

    保留段:用于给系统提供一些必要的空间;
    内核区:由系统使用;

    这里说明一点:栈区从上往下走,堆区会从下往上走,当两个相遇的时候,则会发生堆栈溢出。

        // 一般0x1开头的是 常量 静态  0x7开头的在栈   0x6开头的在堆
    
        NSLog(@"%d - %p",bssA,&bssA); // 0x10efae020 .bss段
        
        NSLog(@"%d - %p",bssB,&bssB); // 0x10efadf50 .data段
        
        int a = 10;
        NSLog(@"%p",&a); // 栈 -- 0x7ffee0c51a6c 栈
        
        NSObject *obj = [NSObject new]; // 对象 --
        NSLog(@"%@ - %p",obj,&obj); // 0x60000270c170> - 0x7ffee0c51a60 对象在堆上 指针在栈上
        
        NSArray *array = [[NSArray alloc] init];
        NSLog(@"%@-%p",array,&array);   //()-0x7ffee0c51a58 
    
    

    普通对象查找过程: 先从栈中找到指针,然后去堆中寻找指针对应的内存空间,进而读取到值。在64位机器上,苹果引进了TaggedPointer的概念。

    TaggedPointer

    为什么要使用taggedPointer
    假设要存储一个NSNumber对象,其值是一个整数。正常情况下,如果这个整数只是一个NSInteger的普通变量,在64位CPU下是占8个字节的。1个字节有8位,如果我们存储一个很小的值,会出现很多位都是0的情况,这样就造成了内存浪费,苹果为了解决这个问题,引入了taggedPointer的概念。

    计算机位运算

    image.png
        //用位运算交换两个数的值
        int a = 2;
        int b = 3;
        exchange(a, b);
    }
    
    void exchange(int a,int b ) {   //a = 0000 0010  b = 0000 0011
        a = a^b;                    //a = 0000 0001
        b = a^b;                    //b = 0000 0010   可以看到 a^b^b = a
        a = a^b;                    //a = 0000 0011
        printf("a=%d b=%d",a,b);
    }
    
    

    有了上面的基础,我们来看一下taggedPointer的源码

    static inline void * _Nonnull
    _objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value)
    {
        if (tag <= OBJC_TAG_Last60BitPayload) {
            uintptr_t result =
                (_OBJC_TAG_MASK | 
                 ((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) | 
                 ((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT));
            return _objc_encodeTaggedPointer(result);
        } else {
            uintptr_t result =
                (_OBJC_TAG_EXT_MASK |
                 ((uintptr_t)(tag - OBJC_TAG_First52BitPayload) << _OBJC_TAG_EXT_INDEX_SHIFT) |
                 ((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT));
            return _objc_encodeTaggedPointer(result);
        }
    }
    static inline void * _Nonnull
    _objc_encodeTaggedPointer(uintptr_t ptr)
    {
        return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr);
    }
    
    static inline uintptr_t
    _objc_decodeTaggedPointer(const void * _Nullable ptr)
    {
        return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
    }
    

    可以看到,系统对taggedPointer进行了 _objc_encodeTaggedPointer 编码,该编码的实现就是对value进行了 objc_debug_taggedpointer_obfuscator 的异或操作,而在读取taggedPointer的时候,通过 _objc_decodeTaggedPointer 进行解码,还是进行了objc_debug_taggedpointer_obfuscator的异或操作,这样进行了两次异或操作就还原了初始值。

    下面我们通过代码来看一下:

        NSNumber * num1 = [NSNumber numberWithInt:100];
        NSNumber * num2 = [NSNumber numberWithInt:200];
        
        NSLog(@"num1 = %@ - %p",num1,&num1);        //num1 = 100 - 0x7ffee0bf5a68
        NSLog(@"num2 = %@ - %p",num2,&num2);        //num2 = 200 - 0x7ffee0bf5a60
    

    根据打印出来的信息,我们并不能分析出有什么特殊的地方,我们来到taggetpointer的init方法

    static void
    initializeTaggedPointerObfuscator(void)
    {
        if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) ||
            // Set the obfuscator to zero for apps linked against older SDKs,
            // in case they're relying on the tagged pointer representation.
            DisableTaggedPointerObfuscation) {
            objc_debug_taggedpointer_obfuscator = 0;
        } else {
            // Pull random data into the variable, then shift away all non-payload bits.
            arc4random_buf(&objc_debug_taggedpointer_obfuscator,
                           sizeof(objc_debug_taggedpointer_obfuscator));
            objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;
        }
    }
    

    可以看到,在Mac10.14和iOS12之前,对taggedpointer做异或的objc_debug_taggedpointer_obfuscator值为0,之后为 objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK这么一步操作,那么我们如何知道系统做了什么呢?
    我们来自己实现taggetpointer的decode,来查看系统在decode之后的数据是怎样的:


    image.png

    然后写一段测试代码:

        int     num1 = 15;
        float   num2 = 11;
        double  num3 = 10;
        long    num4 = 8;
    
        NSNumber * number1 = @(num1);
        NSNumber * number2 = @(num2);
        NSNumber * number3 = @(num3);
        NSNumber * number4 = @(num4);
        
        NSLog(@"number1 = %@ - %@ - %p - 0x%lx",object_getClass(number1),number1,&number1,_objc_decodeTaggedPointer((number1)));
        NSLog(@"number2 = %@ - %@ - %p - 0x%lx",object_getClass(number2),number2,&number2,_objc_decodeTaggedPointer((number2)));
        NSLog(@"number3 = %@ - %@ - %p - 0x%lx",object_getClass(number3),number3,&number3,_objc_decodeTaggedPointer((number3)));
        NSLog(@"number4 = %@ - %@ - %p - 0x%lx",object_getClass(number4),number4,&number4,_objc_decodeTaggedPointer((number4)));
    
        number1 = __NSCFNumber - 15 - 0x7ffee3683a50 - 0xb0000000000000f2
        number2 = __NSCFNumber - 11 - 0x7ffee3683a48 - 0xb0000000000000b4
        number3 = __NSCFNumber - 10 - 0x7ffee3683a40 - 0xb0000000000000a5
        number4 = __NSCFNumber - 8 - 0x7ffee3683a38 - 0xb000000000000083
    

    可以看到,解码之后的真实的数据并不单单是表示值,以number1为例,0xb0000000000000f2,他的真实的值其实是第二位的f(f转10进制就是15),同理,number2,number3,number4都是如此,最后一位 2、4、5、3分别代表int long float double类型

    再来看看string

        NSString * str1 = [NSString stringWithFormat:@"a"];
        NSString * str2 = [NSString stringWithFormat:@"bb"];
        NSString * str3 = [NSString stringWithFormat:@"ccc"];
        NSString * str4 = [NSString stringWithFormat:@"dddd"];
        NSLog(@"str1 = %@ - %p - 0x%lx",object_getClass(str1),str1,_objc_decodeTaggedPointer(str1));
        NSLog(@"str2 = %@ - %p - 0x%lx",object_getClass(str2),str2,_objc_decodeTaggedPointer(str2));
        NSLog(@"str3 = %@ - %p - 0x%lx",object_getClass(str3),str3,_objc_decodeTaggedPointer(str3));
        NSLog(@"str4 = %@ - %p - 0x%lx",object_getClass(str4),str4,_objc_decodeTaggedPointer(str4));
    
        str1 = NSTaggedPointerString - 0xf5ec647546bfddec - 0xa000000000000611
        str2 = NSTaggedPointerString - 0xf5ec647546b9fddf - 0xa000000000062622
        str3 = NSTaggedPointerString - 0xf5ec64754089edce - 0xa000000006363633
        str4 = NSTaggedPointerString - 0xf5ec647300f99db9 - 0xa000000646464644
    

    最后一位表示长度,61、6262、636363、64646464分别对应ASCII的a,bb,ccc,dddd。

    TaggedPointer极大的提高了内存利用率和简化了查询步骤。它不单单是一个指针,还包括了其值+类型,节省了对象的查询流程。

    相关文章

      网友评论

        本文标题:iOS性能优化(内存分布与TaggedPointer)

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