IT评测·应用市场-qidao123.com
标题:
【iOS】—— Tagged Pointer
[打印本页]
作者:
温锦文欧普厨电及净水器总代理
时间:
2024-7-18 19:52
标题:
【iOS】—— Tagged Pointer
关于Tagged Pointer
为了节省内存和提高执行效率,苹果提出了Tagged Pointer的概念。先看看原有的对象为什么会浪费内存。假设要存储一个NSNumber对象,其值是一个整数。正常情况下,如果这个整数只是一个NSInteger的普通变量,那么它所占用的内存是与CPU的位数有关,在32位CPU下占4个字节,在64位CPU下是占8个字节的。而指针类型的大小通常也是与CPU位数相关,一个指针所占用的内存在32位CPU下为4个字节,在64位CPU下也是8个字节。以是一个普通的iOS步调,如果没有Tagged Pointer对象,从32位呆板迁徙到64位呆板中后,虽然逻辑没有任何变化,但这种NSNumber、NSDate一类的对象所占用的内存会翻倍。
Tagged Pointer的先容
为了存储和访问一个NSNumber对象,我们需要在堆上为其分配内存,另外还要维护它的引用计数,管理它的生命期。这些都给步调增加了额外的逻辑,造成运行效率上的损失。
对此提出了Tagged Pointer概念,由于NSNumber、NSDate一类的变量本身的值需要占用的内存大小常常不需要8个字节,拿整数来说,4个字节所能体现的有符号整数就可以达到20多亿。以是我们可以将一个对象的指针拆成两部分,一部分直接保存数据,另一部分作为特殊标记,体现这是一个特别的指针,不指向任何一个地点。
简单来明白就是把指针指向的内容,直接放到了指针变量的内存地点中。
于是使用了标签指针这种方式来优化数据的存储方式。在运行时根据实际情况创建。
NSTaggedPointer示例
NSString *string = nil;
NSMutableString *mutableString = [NSMutableString stringWithFormat:@"abcde"];
for (int i = 0; i < 13; i++) {
[mutableString appendString:@"c"];
string = [mutableString copy];
NSLog(@"%@ %p %@", string, string, [string class]);
}
复制代码
输出结果:
当字符长度在10以内的时候,字符串的类型都是NSTaggedPointer类型,当凌驾10时,就变成了__NSCFString。
NSTaggedPointer结构
苹果为了安全对其做了编码,runtime内部实现了编码、解码方法,我们看一下:
编码:
static inline void * _Nonnull
_objc_encodeTaggedPointer(uintptr_t ptr)
{
uintptr_t value = (objc_debug_taggedpointer_obfuscator ^ ptr);
#if OBJC_SPLIT_TAGGED_POINTERS
if ((value & _OBJC_TAG_NO_OBFUSCATION_MASK) == _OBJC_TAG_NO_OBFUSCATION_MASK)
return (void *)ptr;
uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
uintptr_t permutedTag = _objc_basicTagToObfuscatedTag(basicTag);
value &= ~(_OBJC_TAG_INDEX_MASK << _OBJC_TAG_INDEX_SHIFT);
value |= permutedTag << _OBJC_TAG_INDEX_SHIFT;
#endif
return (void *)value;
}
复制代码
我们可以试着打印地点:
NSNumber *number1 = [NSNumber numberWithInt:1];
NSLog(@"number1 pointer is %p", number1);
复制代码
输出结果:
Tagged Pointer 标记:x86最后一位是标记位,arm64最高位是标记位。1体现是Tagged Pointer对象,0体现是普通对象。
Tag:对象类型标记。x86为13位,arm64为02。7体现有扩展信息。
Extended:x86为411位,arm64为5462。用来扩展更多类型。
payload:有效负载。存储真正的数据(除了标记位、tag以及extended),不过为了安全苹果做了编码。
Tagged Pointer的特点
Tagged Pointer专门用来存储小的对象,比如NSNumber,NSDate。
Tagged Pointer指针指向的不再是内存地点,而是一个真正的值。以是也不是一个对象,而是披着对象外套的变量。内存也不存储在堆上,不需要使用malloc和free。
减少了 64位呆板下步调的内存占用,还提高了运行效率,完美地解决了小内存对象在存储和访问效率上的问题。
注意事项
isa指针
Tagged Pointer 的引入也带来了问题,即 Tagged Pointer 并不是真正的对象,而是一个伪对象,以是你如果完全把它当成对象来使用,可能会让它“露马脚”。在上一章中我们写道,所有对象都有isa 指针,而 Tagged Pointer 其实是没有的,由于它不是真正的对象。
isa指针的优化
除了引入Tagged Pointer来优化小的对象,也普通对象的isa指针进行了优化和调解。
在32 位情况下,对象的引用计数都保存在一个外部的表中,每一个对象的 Retain 操作,实际上包罗如下 5个步骤:
获取全局纪录引用计数的哈希表。
为了线程安全,给哈希表上锁。
找到目标对象的引用计数。
将引用计数+1,写回哈希表。
给该哈希表解锁。
为了保证线程安全,对引用计数的增减操作都要先锁定这个表,这从性能上看是非常差的。
在64位的情况下,指针也是64位,实际作为指针部分的只有33位,剩下的31位中,19位用于保存引用计数,当引用计数凌驾了19位时,才会保存到外部表中,这样引用计数的更改效率提高。
与前面的5个步骤对应,在64位情况下,新的 Retain 操作包罗如下 5个步骤:
检查isa指针是有存在标记位,如果不存在,就执行以前的方法。负责执行的二步。
判断当前的对象是否正在开释,如果是,就不用进行操作。
增加对象的引用计数,先不写回isa指针中。
判断引用计数的位数是否可以被19位体现,如果不能就执行原来的方法,否则执行下一步。
进行原子的写操作,将isa的值写回。
接下来看一道题
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
p.name = [NSString stringWithFormat:@"addafghsdddds"];
});
}
复制代码
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
p.name = [NSString stringWithFormat:@"ad"];
});
}
复制代码
两段代码唯一的区别就是一个name属性所赋值的字符串长一些长度大于10,另一个长度小一点小于10。
我们去运行它,就会发现,第一段代码步调崩溃,第二段没有崩溃。
原因就是:
第一段代码并发访问了共享数据 p.name。在多线程情况下,同时对同一变量进行写操作可能引发竞争条件或数据不同等的问题。要解决它就要给它加上锁。
而第二段由于字符串短,以是被改为了Tagged Pointer对象:Tagged Pointer 指针的值不再是地点了,而是真正的值。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
欢迎光临 IT评测·应用市场-qidao123.com (https://dis.qidao123.com/)
Powered by Discuz! X3.4