[obj autorelease]内部会调用到
rootAutorelease
,其中有个判断prepareOptimizedReturn
返回true的话就直接返回对象,而不去走添加对象到autoreleasepool中这一套逻辑,来提高效率,减少autoreleasepool的开销
本文的runtime版本为objc4-779.1
、设备架构是模拟器x86_64
rootAutorelease
的实现如下:
inline id
objc_object::rootAutorelease()
{
if (isTaggedPointer()) return (id)this; // tagged pointer直接返回不走这一套流程
if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;
return rootAutorelease2();
}
那么系统是怎么做到什么时候去优化,什么时候不去优化了;一时间摸不着头脑,后面看了些文章再结合源码,有了个大概的理解。
1. 分析源码
我们看到上面的代码中有个判断prepareOptimizedReturn
返回true的话就直接return对象不做对应的autorelease操作,那么我们就顺着源码去看看
1.1 prepareOptimizedReturn
// Try to prepare for optimized return with the given disposition (+0 or +1).
// Returns true if the optimized path is successful.
// Otherwise the return value must be retained and/or autoreleased as usual.
static ALWAYS_INLINE bool
prepareOptimizedReturn(ReturnDisposition disposition)
{
ASSERT(getReturnDisposition() == ReturnAtPlus0);
if (callerAcceptsOptimizedReturn(__builtin_return_address(0))) {
if (disposition) setReturnDisposition(disposition);
return true;
}
return false;
}
这里看到核心是判断callerAcceptsOptimizedReturn
为true的时候才返回true,开启优化;那么这里是怎么知道需要优化的了,需要看看callerAcceptsOptimizedReturn
的具体实现;在这之前先了解下__builtin_return_address
和setReturnDisposition
的作用。
1.1.1 __builtin_return_address(0)
作用就是得到函数的返回地址,0--表示返回当前函数的返回地址,1--表示当前函数的调用方的返回地址;具体可参照gcc官方文档的介绍
1.1.2 setReturnDisposition
内部实现如下:
enum ReturnDisposition : bool {
ReturnAtPlus0 = false, ReturnAtPlus1 = true
};
static ALWAYS_INLINE void
setReturnDisposition(ReturnDisposition disposition)
{
tls_set_direct(RETURN_DISPOSITION_KEY, (void*)(uintptr_t)disposition);
}
它的作用就是将disposition的值存储到TLS(Thread Local Storage)中,对应的还有一个读取的方法
static ALWAYS_INLINE ReturnDisposition
getReturnDisposition()
{
return (ReturnDisposition)(uintptr_t)tls_get_direct(RETURN_DISPOSITION_KEY);
}
后面在讲objc_autoreleaseReturnValue
可以看到会存储这个值为ReturnAtPlus1
1.2 callerAcceptsOptimizedReturn
这个函数看名字意思就是调用方是否接受优化的返回,那么编译器就得通过一些手段来知道调用方是怎么处理返回值的,来决定是否去做优化;看源码发现不同的系统架构,该方法的实现也都不一样;
我这里贴的是x86_64的实现
static ALWAYS_INLINE bool
callerAcceptsOptimizedReturn(const void * const ra0)
{
const uint8_t *ra1 = (const uint8_t *)ra0;
const unaligned_uint16_t *ra2;
const unaligned_uint32_t *ra4 = (const unaligned_uint32_t *)ra1;
const void **sym;
#define PREFER_GOTPCREL 0
#if PREFER_GOTPCREL
// 48 89 c7 movq %rax,%rdi
// ff 15 callq *symbol@GOTPCREL(%rip)
if (*ra4 != 0xffc78948) {
return false;
}
if (ra1[4] != 0x15) {
return false;
}
ra1 += 3;
#else
// 48 89 c7 movq %rax,%rdi
// e8 callq symbol
if (*ra4 != 0xe8c78948) {
return false;
}
ra1 += (long)*(const unaligned_int32_t *)(ra1 + 4) + 8l;
ra2 = (const unaligned_uint16_t *)ra1;
// ff 25 jmpq *symbol@DYLDMAGIC(%rip)
if (*ra2 != 0x25ff) {
return false;
}
#endif
ra1 += 6l + (long)*(const unaligned_int32_t *)(ra1 + 2);
sym = (const void **)ra1;
// 这里检验了主调方在返回值之后是否紧接着调用了以下2个方法去持有返回的对象,如果有则说明可以去优化,不需要被调用方去autorelease,也不需要调用方去retain返回的对象了,省去了开销
if (*sym != objc_retainAutoreleasedReturnValue &&
*sym != objc_unsafeClaimAutoreleasedReturnValue)
{
return false;
}
return true;
}
看代码当调用方在返回值之后如果调用了objc_retainAutoreleasedReturnValue
或者objc_unsafeClaimAutoreleasedReturnValue
的时候就表示可以优化;
这2个函数的具体实现如下:
// Accept a value returned through a +0 autoreleasing convention for use at +1.
id
objc_retainAutoreleasedReturnValue(id obj)
{
if (acceptOptimizedReturn() == ReturnAtPlus1) return obj; // 调用方去TLS中查找刚好标记位为true,那么就直接返回该对象了。省去了retain的操作
return objc_retain(obj);
}
// Accept a value returned through a +0 autoreleasing convention for use at +0.
id
objc_unsafeClaimAutoreleasedReturnValue(id obj)
{
if (acceptOptimizedReturn() == ReturnAtPlus0) return obj;
return objc_releaseAndReturn(obj); // release(obj) and return obj
}
acceptOptimizedReturn
则去读取TLS中存储的值,同时读取之后也会置为ReturnAtPlus0
static ALWAYS_INLINE ReturnDisposition
acceptOptimizedReturn()
{
ReturnDisposition disposition = getReturnDisposition(); // 从tsl中读取对应的值
setReturnDisposition(ReturnAtPlus0); // reset to the unoptimized state 将数据之为0
return disposition;
}
objc_retainAutoreleasedReturnValue
表示调用方是强持有返回的对象,而objc_unsafeClaimAutoreleasedReturnValue
表示调用方不强持有返回的对象;
这里可以这么去理解:
- 假如编译器知道调用方是强持有返回的值(ref + 1),那么就没必要返回的时候autorelease一下,然后调用方再将返回值retain一下,直接返回对象就好了
- 假如调用方不强持有返回的值,那么返回值不加入autoreleasepool中的话,就需要
objc_releaseAndReturn
release该对象了,省去了加入autoreleasepool的操作
2.示例解析
2.1 强持有的情况
测试代码:
@implementation TestClass
+ (instancetype)testInstance {
return [self new];
}
@end
// 测试用例:
void autoreleaseTestCase(void) {
TestClass *test = [TestClass testInstance]; // test的默认修饰符是strong
}
查看汇编代码:
// 被调用方实现:
define internal i8* @"\01+[TestClass testInstance]"(i8* %0, i8* %1) #1 !dbg !72 {
%3 = alloca i8*, align 8
%4 = alloca i8*, align 8
store i8* %0, i8** %3, align 8
call void @llvm.dbg.declare(metadata i8** %3, metadata !83, metadata !DIExpression()), !dbg !85
store i8* %1, i8** %4, align 8
call void @llvm.dbg.declare(metadata i8** %4, metadata !86, metadata !DIExpression()), !dbg !85
%5 = load i8*, i8** %3, align 8, !dbg !88
%6 = call i8* @objc_opt_new(i8* %5), !dbg !89
%7 = bitcast i8* %6 to %1*, !dbg !89
%8 = bitcast %1* %7 to i8*, !dbg !89
%9 = tail call i8* @llvm.objc.autoreleaseReturnValue(i8* %8) #3, !dbg !90
ret i8* %9, !dbg !90
}
// 调用方实现:
define void @autoreleaseTestCase() #4 !dbg !311 {
%1 = alloca %1*, align 8
call void @llvm.dbg.declare(metadata %1** %1, metadata !312, metadata !DIExpression()), !dbg !313
%2 = load %struct._class_t*, %struct._class_t** @"OBJC_CLASSLIST_REFERENCES_$_.63", align 8, !dbg !314
%3 = load i8*, i8** @OBJC_SELECTOR_REFERENCES_.69, align 8, !dbg !314, !invariant.load !8
%4 = bitcast %struct._class_t* %2 to i8*, !dbg !314
%5 = call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)(i8* %4, i8* %3), !dbg !314
%6 = notail call i8* @llvm.objc.retainAutoreleasedReturnValue(i8* %5) #3, !dbg !314
%7 = bitcast i8* %6 to %1*, !dbg !314
store %1* %7, %1** %1, align 8, !dbg !314
%8 = bitcast %1** %1 to i8**, !dbg !315
call void @llvm.objc.storeStrong(i8** %8, i8* null) #3, !dbg !315
ret void, !dbg !315
}
在看看被调用方中的objc_autoreleaseReturnValue
实现:
id
objc_autoreleaseReturnValue(id obj) // return [obj autorelease] ??
{
if (prepareOptimizedReturn(ReturnAtPlus1)) return obj; // 这里判断如果调用方是objc_retainAutoreleasedReturnValue,那么就说明外部是对obj retain操作,那么这里就不需要将对象加到autorelease中,然后外部调用方又去retain一下,直接返回回去该对象,节省了这些开销
return objc_autorelease(obj);
}
这里调用方在返回值调用了retainAutoreleasedReturnValue
,那么被调用方在prepareOptimizedReturn
操作就会存储true到TLS中,并且直接返回了obj;同时调用方在执行retainAutoreleasedReturnValue
读到了TLS中存储的值为true,则表示不需要去retain操作了,这样就省去了被调用方返回的时候加入autoreleasepool的操作,以及调用方retain返回值的操作
2.2 非强持有
测试代码:
void autoreleaseTestCase1(void) {
// 这里我们对返回值不强持有
__unsafe_unretained id testObj = [TestClass testInstance];
}
查看汇编代码:
// 被调用方:
define internal i8* @"\01+[TestClass testInstance]"(i8* %0, i8* %1) #1 !dbg !72 {
%3 = alloca i8*, align 8
%4 = alloca i8*, align 8
store i8* %0, i8** %3, align 8
call void @llvm.dbg.declare(metadata i8** %3, metadata !83, metadata !DIExpression()), !dbg !85
store i8* %1, i8** %4, align 8
call void @llvm.dbg.declare(metadata i8** %4, metadata !86, metadata !DIExpression()), !dbg !85
%5 = load i8*, i8** %3, align 8, !dbg !88
%6 = call i8* @objc_opt_new(i8* %5), !dbg !89
%7 = bitcast i8* %6 to %1*, !dbg !89
%8 = bitcast %1* %7 to i8*, !dbg !89
%9 = tail call i8* @llvm.objc.autoreleaseReturnValue(i8* %8) #3, !dbg !90
ret i8* %9, !dbg !90
}
// 调用方:
define void @autoreleaseTestCase1() #4 !dbg !145 {
%1 = alloca i8*, align 8
call void @llvm.dbg.declare(metadata i8** %1, metadata !146, metadata !DIExpression()), !dbg !147
%2 = load %struct._class_t*, %struct._class_t** @"OBJC_CLASSLIST_REFERENCES_$_.63", align 8, !dbg !148
%3 = load i8*, i8** @OBJC_SELECTOR_REFERENCES_.69, align 8, !dbg !148, !invariant.load !8
%4 = bitcast %struct._class_t* %2 to i8*, !dbg !148
%5 = call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)(i8* %4, i8* %3), !dbg !148
%6 = call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %5) #3, !dbg !148
%7 = bitcast i8* %6 to %1*, !dbg !148
%8 = bitcast %1* %7 to i8*, !dbg !148
store i8* %8, i8** %1, align 8, !dbg !148
ret void, !dbg !149
}
上面也分析过被调用方autoreleaseReturnValue
在调用方调用了unsafeClaimAutoreleasedReturnValue
也会通过prepareOptimizedReturn(ReturnAtPlus1)
将true写入TLS中,免去了将返回值加入autoreleasepool的操作,而unsafeClaimAutoreleasedReturnValue
在从TLS中取值的时候就不会走这个分支if (acceptOptimizedReturn() == ReturnAtPlus0) return obj;
而走了return objc_releaseAndReturn(obj)
,这样对象也正常release了。
3. 总结
- 编译器通过
__builtin_return_address
来知道调用方是强持有或者非强持有autoreleased返回值来决定去优化 - 被调用方通过向TLS存储
ReturnAtPlus1
标记位表示做了优化,调用方读取该标记位的值,来决定是否需要retain及release操作;从而省去了将对象加入到autoreleasepool的操作
4. 参考文档
- TLS文档
- __builtin_return_address