Search code examples
iosnsautoreleasepool

@autoreleasepool block in loop dose not reduce memory peak


I was told that @autoreleasepool block in loop can reduce the peak of memory usage until I do a test. The test device is iPhone 6s with iOS 11.4.1.

my code:

@implementation BigMemObj{
    NSMutableArray *_mutArr;
}

-(instancetype)init{
    if(self = [super init]){
        _mutArr = [[NSMutableArray alloc] initWithCapacity:1024*1024*30];
        for(int i = 0; i < 1024*1024*30; i++){
            [_mutArr addObject:@(i)];
        }
    }

    return self;
}


- (void)viewDidLoad {

    NSMutableArray *arr = [[NSMutableArray alloc] init];
    for(int i = 0 ; i < 10000; i++){
        @autoreleasepool {
            BigMemObj *mem = [[BigMemObj alloc] init];
        }
    }
}

- (void)viewDidLoad {

    NSMutableArray *arr = [[NSMutableArray alloc] init];
    for(int i = 0 ; i < 10000; i++){
           BigMemObj *mem = [[BigMemObj alloc] init];
    }
}

I run both test 34 seconds, in test 1, the highest memory usage is 458M, but in the test 2 the highest memory usage is 362M. and both test have a triangle shape.

with @autoreleaspool block

enter image description here

without @autoreleaspool block enter image description here

Did the autoreleasepool implementation changed? or the compiler dose some optimization?

Thank You!


Solution

  • It all looks normal actually. The growth you are seeing is this part:

    _mutArr = [[NSMutableArray alloc] initWithCapacity:1024*1024*30];
    for(int i = 0; i < 1024*1024*30; i++){
        [_mutArr addObject:@(i)];
    }
    

    So here you are adding your numbers to an array _mutArr and you are adding 1024*1024*30 of them. When this loop finishes the _mutArr is valid and full, it retains all of those numbers. This would not even be changed by adding another autorelease pool within this loop because your array will not let those numbers be released.

    Now after this constructor is being called you have

    @autoreleasepool {
        BigMemObj *mem = [[BigMemObj alloc] init];
    }
    

    so autorelease pool will be drained in this moment releasing all the numbers inside BigMemObj instance mem and your memory is back to normal.

    You might expect that your application should keep growing in memory without the call to @autoreleasepool. But there is no change at all if you remove that call. The reason for that is that none of your code uses autorelease pool at all. What your code translates to (non-ARC) is:

    NSMutableArray *arr = [[NSMutableArray alloc] init];
    for(int i = 0 ; i < 10000; i++){
        @autoreleasepool {
            BigMemObj *mem = [[BigMemObj alloc] init];
            [mem release];
        }
    }
    [arr release];
    

    But you would need your autoreleasepool only if it was

    NSMutableArray *arr = [[NSMutableArray alloc] init];
    for(int i = 0 ; i < 10000; i++){
        @autoreleasepool {
            BigMemObj *mem = [[[BigMemObj alloc] init] autorelease];
        }
    }
    [arr release];
    

    To have a situation where autorelease pool is needed:

    NSMutableArray *allBigValues = [[NSMutableArray alloc] init];
    
    NSString *path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"profile.png"];
    
    for(int i = 0; i<100000; i++){
        @autoreleasepool {
            [allBigValues addObject:[UIImage imageWithContentsOfFile:path]];
            [allBigValues removeAllObjects];
        }
    }
    

    If in this code you remove the autorelease pool it will grow in memory until the loop ends. The reason for this is because imageWithContentsOfFile is using autorelease pool and all the images produced by this method will only be released after the pool is drained. Since the main pool will not be released inside the loop we need to create another one.

    Bottom line this code works nicely but as soon as you remove the @autoreleasepool part it will start growing in memory and probably crash you application.

    Note 1: You need to add image profile.png into your app for this code to work (just drag it among source files, not the assets).

    Note 2: We use "drain" where it comes to pools because this used to be the name of the method you needed to call when you wanted for the pool to remove it's objects. This is how it used to be:

    for(int i = 0; i<100000; i++){
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        [allBigValues addObject:[UIImage imageWithContentsOfFile:path]];
        [allBigValues removeAllObjects];
        [pool drain];
    }