【问题标题】:Lack of autorelease optimization under ARC compilerARC编译器下缺乏自动释放优化
【发布时间】:2014-01-16 20:20:21
【问题描述】:

我只是想知道,为什么 ARC 编译器下没有自动释放池优化,它会在最内部的范围内保留一个对象,将其从自动释放池中删除并在对象不再使用时释放?

从另一个问题中引用一个非常不切实际的例子,

for(NSUInteger i = 0; i < 10000; i++)
{
    for(NSUInteger j = 0; j < 10000; j++)
    {
        NSNumber* n = [NSNumber numberWithUnsignedInteger:j];
        //NSLog(@"%@", n); //Disabled this to increase memory bloat faster.
    }
}

如果没有@autoreleasepool { ... } 包装,内存会不断增长。用@autoreleasepool包裹,内存仍然很低:

for(NSUInteger i = 0; i < 10000; i++)
{
    for(NSUInteger j = 0; j < 10000; j++)
    {
        @autoreleasepool {
            NSNumber* n = [NSNumber numberWithUnsignedInteger:j];
            //NSLog(@"%@", n); //Disabled this to increase memory bloat faster.
        }
    }
}

但是为什么编译器不能优化这样的情况,在最内层范围之外不需要对象并删除@autoreleasepool 包装的需要?是否有技术原因这不可能或尚未完成?

编辑

澄清一下,为什么编译器不能输出如下代码:

for(NSUInteger i = 0; i < 10000; i++)
{
    for(NSUInteger j = 0; j < 10000; j++)
    {
        NSNumber* n = [NSNumber numberWithUnsignedInteger:j];
        objc_retain(n);
        objc_removeFromAutoreleasePool(n);
        NSLog(@"%@", n);
        objc_release(n);
    }
}

编辑 2

应格雷格的要求,这里是上面两个例子的反汇编结果。

没有@autoreleasepool { }

TestOpt`-[LMViewController testAutoreleaseMem] at LMViewController.m:17:
0x2187:  pushl  %ebp
0x2188:  movl   %esp, %ebp
0x218a:  pushl  %ebx
0x218b:  pushl  %edi
0x218c:  pushl  %esi
0x218d:  subl   $0x1c, %esp
0x2190:  calll  0x2195                    ; -[LMViewController testAutoreleaseMem] + 14 at LMViewController.m:17
0x2195:  popl   %esi
0x2196:  xorl   %eax, %eax
0x2198:  movl   0x13cb(%esi), %ebx
0x219e:  movl   %eax, -0x10(%ebp)
0x21a1:  xorl   %edi, %edi
0x21a3:  movl   0x13df(%esi), %eax
0x21a9:  movl   %edi, 0x8(%esp)
0x21ad:  movl   %ebx, 0x4(%esp)
0x21b1:  movl   %eax, (%esp)
0x21b4:  calll  0x227e                    ; symbol stub for: objc_msgSend
0x21b9:  movl   %eax, (%esp)
0x21bc:  calll  0x2296                    ; symbol stub for: objc_retainAutoreleasedReturnValue
0x21c1:  movl   %eax, (%esp)
0x21c4:  calll  0x228a                    ; symbol stub for: objc_release
0x21c9:  incl   %edi
0x21ca:  cmpl   $0x2710, %edi
0x21d0:  jne    0x21a3                    ; -[LMViewController testAutoreleaseMem] + 28 at LMViewController.m:24
0x21d2:  movl   -0x10(%ebp), %eax
0x21d5:  incl   %eax
0x21d6:  cmpl   $0x2710, %eax
0x21db:  jne    0x219e                    ; -[LMViewController testAutoreleaseMem] + 23 at LMViewController.m:24
0x21dd:  addl   $0x1c, %esp
0x21e0:  popl   %esi
0x21e1:  popl   %edi
0x21e2:  popl   %ebx
0x21e3:  popl   %ebp
0x21e4:  ret    

与:

TestOpt`-[LMViewController testAutoreleaseMem] at LMViewController.m:17:
0x216f:  pushl  %ebp
0x2170:  movl   %esp, %ebp
0x2172:  pushl  %ebx
0x2173:  pushl  %edi
0x2174:  pushl  %esi
0x2175:  subl   $0x1c, %esp
0x2178:  calll  0x217d                    ; -[LMViewController testAutoreleaseMem] + 14 at LMViewController.m:17
0x217d:  popl   %ecx
0x217e:  movl   %ecx, -0x10(%ebp)
0x2181:  xorl   %eax, %eax
0x2183:  movl   0x13e3(%ecx), %ecx
0x2189:  movl   %eax, -0x14(%ebp)
0x218c:  xorl   %edi, %edi
0x218e:  movl   %ecx, %ebx
0x2190:  calll  0x2278                    ; symbol stub for: objc_autoreleasePoolPush
0x2195:  movl   %eax, %esi
0x2197:  movl   -0x10(%ebp), %eax
0x219a:  movl   0x13f7(%eax), %eax
0x21a0:  movl   %edi, 0x8(%esp)
0x21a4:  movl   %ebx, 0x4(%esp)
0x21a8:  movl   %eax, (%esp)
0x21ab:  calll  0x227e                    ; symbol stub for: objc_msgSend
0x21b0:  movl   %eax, (%esp)
0x21b3:  calll  0x2296                    ; symbol stub for: objc_retainAutoreleasedReturnValue
0x21b8:  movl   %eax, (%esp)
0x21bb:  calll  0x228a                    ; symbol stub for: objc_release
0x21c0:  movl   %esi, (%esp)
0x21c3:  calll  0x2272                    ; symbol stub for: objc_autoreleasePoolPop
0x21c8:  incl   %edi
0x21c9:  cmpl   $0x2710, %edi
0x21cf:  jne    0x2190                    ; -[LMViewController testAutoreleaseMem] + 33 at LMViewController.m:23
0x21d1:  movl   %ebx, %ecx
0x21d3:  movl   -0x14(%ebp), %eax
0x21d6:  incl   %eax
0x21d7:  cmpl   $0x2710, %eax
0x21dc:  jne    0x2189                    ; -[LMViewController testAutoreleaseMem] + 26 at LMViewController.m:24
0x21de:  addl   $0x1c, %esp
0x21e1:  popl   %esi
0x21e2:  popl   %edi
0x21e3:  popl   %ebx
0x21e4:  popl   %ebp
0x21e5:  ret    

【问题讨论】:

  • 如果你使用了 alloc/initWith... 它将有机会插入一个释放... +numberWith 将始终返回一个自动释放的对象。所以它将被池保留。
  • @GradyPlayer 我知道它目前是如何工作的。我在问为什么不能从池中删除对象,以便在范围结束后释放?
  • 我认为 ARC 能正常工作是一个奇迹。我们不要挑剔。
  • @HotLicks 没什么神奇的,只有精细的工程和良好的规则集。只是想知道是否在某些情况下无法进行这种优化。但 Rob 的建议很有趣。

标签: objective-c automatic-ref-counting llvm


【解决方案1】:

任何“从自动释放池中删除”操作都是低效的。自动释放池只是一个稍后释放的指针数组。没有快速的方法来检查池中是否存在指针。

ARC 确实有一个优化,可以从被调用者执行return [obj autorelease] 的某些情况中删除自动释放。在我的测试中,这将 -[NSNumber numberWithUnsignedInteger:] 的自动释放池开销减少到零。

某些版本的 OS X 或 iOS 可能以阻止 ARC 的 return-autorelease 优化的方式实现 -numberWithUnsignedInteger:。也有可能是某些编译器版本在返回的对象未被使用时无法执行return-autorelease优化。

在您的原始测试中,NSLog() 的内部实现生成了自动释放的对象。除了将每个函数调用或每个循环包装在自动释放池中之外,ARC 无能为力。

【讨论】:

  • 感谢您的回答。我实际上删除了NSLog 调用以使内存迅速增加。我在 x86_64 上使用 Xcode 5.1b2 和 iOS7.1SDK 进行了测试。使用自动释放池进行自动包装怎么样?自动释放池的推送/弹出开销是多少?
  • 自动释放池相对便宜,但不足以将它们添加到任何地方。你能发布一个刚刚运行该循环的函数的 Release 版本的反汇编吗?
  • @LeoNatan 确保在启用优化的情况下进行测试,因为优化器会影响释放/保留/自动释放的使用。它可能不会影响这种特定情况,但也可能会导致显着不同的输出。
  • @bbum 优化已启用;优化级别:-Os
【解决方案2】:

为什么您认为 ARC 没有优化上述代码?在 Instruments 下的 Release 模式下尝试。堆没有增长。如果您在 Debug 下进行测试,那么问题在于您没有使用优化器。

【讨论】:

  • release模式下,-Os优化,内存不断增加。
  • 你能发表一个完整的要点吗?我已经对此进行了分析,但没有任何内存增长。 gist.github.com/rnapier/8466934
【解决方案3】:

您可能可以将功能分解为多个调用,这将使代码有时间呼吸。就像让一个方法一次执行 x 一样,当该方法返回时,它可能会释放该内存。

【讨论】:

  • 这不是问题的答案,在此示例中不正确。该示例只是作为快速增加内存和演示要点的手段而给出的。
猜你喜欢
  • 2013-07-10
  • 1970-01-01
  • 1970-01-01
  • 2016-08-05
  • 2012-01-07
  • 1970-01-01
  • 1970-01-01
  • 2012-03-16
  • 2013-09-07
相关资源
最近更新 更多