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

434 阅读5分钟

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

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

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

// 一般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极大的提高了内存利用率和简化了查询步骤。它不单单是一个指针,还包括了其值+类型,节省了对象的查询流程。

作者:聪莞 链接:www.jianshu.com/p/3356fe2dd… 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。