Ios 为什么要实施ARC';x86_64和ARM的s objc_AutoreleaseReturn值不同?
在阅读了Mike Ash关于ARC的优秀博文后,我决定查看ARC为加速保留/发布过程而应用的优化的细节。 我所指的技巧称为“快速自动释放”,其中调用者和被调用者合作将返回的对象从自动释放池中排除。这在以下情况下效果最佳:Ios 为什么要实施ARC';x86_64和ARM的s objc_AutoreleaseReturn值不同?,ios,objective-c,memory-management,assembly,automatic-ref-counting,Ios,Objective C,Memory Management,Assembly,Automatic Ref Counting,在阅读了Mike Ash关于ARC的优秀博文后,我决定查看ARC为加速保留/发布过程而应用的优化的细节。 我所指的技巧称为“快速自动释放”,其中调用者和被调用者合作将返回的对象从自动释放池中排除。这在以下情况下效果最佳: - (id) myMethod { id obj = [MYClass new]; return [obj autorelease]; } - (void) mainMethod { obj = [[self myMethod] retain];
- (id) myMethod {
id obj = [MYClass new];
return [obj autorelease];
}
- (void) mainMethod {
obj = [[self myMethod] retain];
// Do something with obj
[obj release];
}
可通过完全跳过自动释放池进行优化:
- (id) myMethod {
id obj = [MYClass new];
return obj;
}
- (void) mainMethod {
obj = [self myMethod];
// Do something with obj
[obj release];
}
这种优化的实施方式非常有趣。我引用迈克的帖子:
“在Objective-C运行时的autorelease实现中,有一些非常奇特和令人费解的代码。在实际发送自动释放消息之前,它首先检查调用方的代码。如果它看到调用方将立即调用objc_retainAutoreleasedReturnValue,它将完全跳过消息send。它实际上根本没有自动释放功能。相反,它只是将对象存储在已知位置,这表示它根本没有发送自动释放。”
到目前为止还不错。NSObject.mm上x86_64的实现非常简单。代码分析位于objc_autoreleaserereturnvalue
返回地址之后的汇编程序,以查看是否存在对objc_retainatoreleasedreturnvalue
的调用
static bool callerAcceptsFastAutorelease(const void * const ra0)
{
const uint8_t *ra1 = (const uint8_t *)ra0;
const uint16_t *ra2;
const uint32_t *ra4 = (const uint32_t *)ra1;
const void **sym;
//1. Navigate the DYLD stubs to get to the real pointer of the function to be called
// 48 89 c7 movq %rax,%rdi
// e8 callq symbol
if (*ra4 != 0xe8c78948) {
return false;
}
ra1 += (long)*(const int32_t *)(ra1 + 4) + 8l;
ra2 = (const uint16_t *)ra1;
// ff 25 jmpq *symbol@DYLDMAGIC(%rip)
if (*ra2 != 0x25ff) {
return false;
}
ra1 += 6l + (long)*(const int32_t *)(ra1 + 2);
sym = (const void **)ra1;
//2. Check that the code to be called belongs to objc_retainAutoreleasedReturnValue
if (*sym != objc_retainAutoreleasedReturnValue)
{
return false;
}
return true;
}
但是说到ARM,我就是不明白它是如何工作的。代码看起来像这样(我简化了一点):
代码似乎不是通过查找对该特定函数调用的存在,而是通过查找特殊的无操作操作来识别是否存在objcretainatoreleasedReturnValue
深入研究LLVM源代码,我发现了以下解释:
“objc_autoreleaseReturnValue的实现嗅探返回地址后的指令流,以确定它是否是对objc_RetainUserReleaseReturnValue的调用。这可能会非常昂贵,具体取决于重新定位模型,因此在某些目标上,它会嗅探特定的指令序列。此函数返回内联程序集中的指令序列,如果不需要,则该指令序列将为空。”
我想知道为什么手臂上会这样
让编译器在那里放置一个特定的标记,以便库的特定实现可以发现它听起来像是编译器和库代码之间的强耦合。为什么“嗅探”不能以与x86_64平台相同的方式实现呢?IIRC(我写ARM汇编已经有一段时间了),ARM的寻址模式实际上不允许跨整个地址空间直接寻址。用于寻址的指令(加载、存储等)不支持直接访问整个地址空间,因为它们的位宽度有限
因此,任何类型的访问这个任意地址并检查该值,然后使用该值去查看,在ARM上的速度都会大大降低,因为您必须使用间接寻址,这涉及到数学和…数学消耗CPU周期
通过让编译器发出易于检查的NO-OP指令,它消除了通过DYLD存根进行间接寻址的需要
至少,我很确定这就是正在发生的事情。有两种方法可以确定:获取这两个函数的代码,并使用-Os for x86_64与ARM进行编译,然后查看生成的指令流是什么样的(即每个体系结构上的两个函数)或者等到Greg Parker出现来纠正这个答案。另一个区别。在Intel上,解析的dyld存根很简单:它只是一个分支到一个分支的分支。在ARM上,分支到存根的分支和存根的分支的指令序列可以根据分支的长度采取许多不同的形式。检查每个组合uld速度较慢。还应注意存在“强耦合”“在两个版本的编译器和库之间。例如,在英特尔上,编译器优化器不得在call/mov/call序列中调度任何其他指令。@GregParker-Ah!这很有道理。但是为什么不简化callerAcceptsFastAutorelease的代码并在x86_64上使用ARM标记呢?使用magic指令更难实现。在我们开始使用ARM版本之前,我们没有考虑它,在那时候改变英特尔版本已经太晚了。
static bool callerAcceptsFastAutorelease(const void *ra)
{
// 07 70 a0 e1 mov r7, r7
if (*(uint32_t *)ra == 0xe1a07007) {
return true;
}
return false;
}