Lack of autorelease optimization under ARC compile

2020-04-23 11:41发布

问题:

I was just wondering, why is there no autorelease pool optimization under the ARC compiler, where it would retain an object in the innermost scope, remove it from the autorelease pool and release once the object is no longer in use?

To quote a very impractical example from another question,

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.
    }
}

Without @autoreleasepool { ... } wrapping, memory grows and grows. Wrapping with @autoreleasepool, memory remains low:

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.
        }
    }
}

But why can't the compiler optimize cases like these, where the objects will not be needed beyond the innermost scope and remove the need of the @autoreleasepool wrapping? Is there a technical reason this is not possible or has not been done yet?

Edit

To clarify, why can't the compiler output a code like the following:

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);
    }
}

Edit 2

At the request of Greg, here are the disassembly results of both examples above.

Without @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    

With:

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    

回答1:

Any "remove from autorelease pool" operation would be inefficient. An autorelease pool is simply an array of pointers to release later. There's no fast way to check if a pointer is present in the pool.

ARC does have an optimization that can remove the autorelease from some cases where the callee performed return [obj autorelease]. In my tests this reduces the autorelease pool overhead of -[NSNumber numberWithUnsignedInteger:] to zero.

It's possible that some versions of OS X or iOS implement -numberWithUnsignedInteger: in a way that prevents ARC's return-autorelease optimization. It's also possible that some compiler versions fail to perform the return-autorelease optimization when the returned object is unused.

In your original test, the internal implementation of NSLog() generated the autoreleased objects. There's nothing ARC can do about that short of wrapping every function call or every loop in an autorelease pool.



回答2:

Why do you believe that ARC doesn't optimize the above code? Try it in Release mode under Instruments. The heap doesn't grow. If you're testing under Debug, then the problem is that you're not engaging the optimizer.



回答3:

You could probably break the functionality up into multiple calls which would give the code time to breath. Like having a method do x at a time, and when that method returned it would likely release that memory.