iOS底层小记一:关于Tagged Pointer

微信扫一扫,分享到朋友圈

iOS底层小记一:关于Tagged Pointer

背景

  • 64bits 开始, iOS引入了 Tagged Pointer 技术,以此优化 NSNumber、NSString、NSDate 等小对象的存储。

好处

  • 在引入 Tagged Pointer 技术之前、 NSNumber 等对象的存储需要动态分配内存、维护引用计数等, NSNumber 的指针存储的是堆中 NSNumber 对象的地址值。

    也就是说,仅仅只是为了存储一个 1 的整数值,就需要去堆区动态开辟内存,以及引用计数来管理这块内存,总共花费24个字节不觉得这很滑稽、很浪费么?

于是乎,苹果爸爸一拍脑袋,把 NSNumber ( NSString、NSDate )的数据直接放进了指针里!以此避免了没必要的内存开销,以及内存管理的花销。当指针不够存储数据的时候、才会动态分配内存去存储数据。

Tagged Pointer = Tag + Data (地址 + 数据)

举个:chestnut:

int main(int argc, const char * argv[]) {
@autoreleasepool {
NSNumber *number1 = @1;
NSNumber *number2 = @2;
NSNumber *number3 = @999999999999999999;
NSLog(@"number1 = %p", number1);
NSLog(@"number2 = %p", number2);
NSLog(@"number3 = %p", number3);
}
return 0;
}
2020-09-29 17:58:57.252080+0800 MemoryTest[95240:2528802] number1 = 0xdfc933f566855f8e
2020-09-29 17:58:57.252517+0800 MemoryTest[95240:2528802] number2 = 0xdfc933f566855fbe
2020-09-29 17:58:57.252578+0800 MemoryTest[95240:2528802] number3 = 0x6000039f0080
复制代码

0xdfc933f566855f8e0xdfc933f566855fbe 可见是一个栈区的地址(因为地址比较大), 0x6000039f0080 是一个堆区地址, number3 = 999999999999999999 超出了 8个字节 承载的范围,故而成了一个普通的指针。

  • WWDC2013 的《Session 404 Advanced in Objective-C》视频中,看到苹果对于Tagged Pointer特点的介绍:

  • Tagged Pointer专门用来存储小的对象,例如NSNumber和NSDate Tagged Pointer指针的值不再是地址了,而是真正的值。

  • 实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储在堆中,也不需要 malloc 和 free。

  • 在内存读取上有着 3 倍的效率,创建时比以前快 106 倍。

由此可见,苹果引入Tagged Pointer,不但减少了 64 位机器下程序的内存占用,还提高了运行效率。完美地解决了小内存对象在存储和访问效率上的问题。

窥探源码

  • 如何判断一个指针是不是 Tagged Pointer

runtime的源码objc-internal.h

objc_object::isTaggedPointer()
{
return _objc_isTaggedPointer(this);
}
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
复制代码

我们注意到这个掩码 _OBJC_TAG_MASK ,找到它的定义:

#if (TARGET_OS_OSX || TARGET_OS_IOSMAC) && __x86_64__ // iOS系统下
// 64-bit Mac - tag bit is LSB
#   define OBJC_MSB_TAGGED_POINTERS 0
#else // macOS系统下
// Everything else - tag bit is MSB
#   define OBJC_MSB_TAGGED_POINTERS 1
#endif
#if OBJC_MSB_TAGGED_POINTERS
#   define _OBJC_TAG_MASK (1UL<<63)
#   define _OBJC_TAG_INDEX_SHIFT 60
#   define _OBJC_TAG_SLOT_SHIFT 60
#   define _OBJC_TAG_PAYLOAD_LSHIFT 4
#   define _OBJC_TAG_PAYLOAD_RSHIFT 4
#   define _OBJC_TAG_EXT_MASK (0xfUL<<60)
#   define _OBJC_TAG_EXT_INDEX_SHIFT 52
#   define _OBJC_TAG_EXT_SLOT_SHIFT 52
#   define _OBJC_TAG_EXT_PAYLOAD_LSHIFT 12
#   define _OBJC_TAG_EXT_PAYLOAD_RSHIFT 12
#else
#   define _OBJC_TAG_MASK 1UL
#   define _OBJC_TAG_INDEX_SHIFT 1
#   define _OBJC_TAG_SLOT_SHIFT 0
#   define _OBJC_TAG_PAYLOAD_LSHIFT 0
#   define _OBJC_TAG_PAYLOAD_RSHIFT 4
#   define _OBJC_TAG_EXT_MASK 0xfUL
#   define _OBJC_TAG_EXT_INDEX_SHIFT 4
#   define _OBJC_TAG_EXT_SLOT_SHIFT 4
#   define _OBJC_TAG_EXT_PAYLOAD_LSHIFT 0
#   define _OBJC_TAG_EXT_PAYLOAD_RSHIFT 12
#endif
复制代码

我们注意到在不同的系统下,掩码 _OBJC_TAG_MASK 的定义有所不同:

在iOS系统下( __x86_64__ ), _OBJC_TAG_MASK1UL<<63 ,等于是8字节地址的最高有效位1;

在macOS系统下, _OBJC_TAG_MASK1UL ,等于是8字节地址的最低有效位1;

((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK; 做一次位运算,取到对应位上的数据,是否等于掩码,以此判断是否为 Tagged Pointer`。

  • 无处不在的 Tagged Pointer 随意摘几处 runtime 的源码:

判断是否是类对象:

// objc-object.h
objc_object::isClass()
{
if (isTaggedPointer()) return false;
return ISA()->isMetaClass();
}
复制代码

: 所以 Tagged Pointer 不再是个对象

inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return;  // fixme necessary?
if (fastpath(isa.nonpointer  &&
!isa.weakly_referenced  &&
!isa.has_assoc  &&
!isa.has_cxx_dtor  &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
复制代码

: 当一个对象释放的时候,先判断是否为 Tagged Pointer ,因为 Tagged Pointer 不需要程序员去管理内存,系统帮你回收( 栈区内存归系统管理,不多说 )。

inline id
objc_object::retain()
{
ASSERT(!isTaggedPointer());
if (fastpath(!ISA()->hasCustomRR())) {
return rootRetain();
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain));
}
objc_release(id obj)
{
if (!obj) return;
if (obj->isTaggedPointer()) return;
return obj->release();
}
复制代码

: Tagged Pointer 无法被强引用,也没有引用计数的说法。

参考链接

www.mikeash.com/pyblog/frid…

微信扫一扫,分享到朋友圈

iOS底层小记一:关于Tagged Pointer

俞敏洪回访香格里拉新东方希望小学 计划接入英语双师直播课堂

上一篇

排查 Node.js 服务内存泄漏,没想到竟是它?

下一篇

你也可能喜欢

iOS底层小记一:关于Tagged Pointer

长按储存图像,分享给朋友