James Hunt
James Hunt

Reputation: 15

autoreleased objects freed, still running out of memory?

http://i46.tinypic.com/2nquzag.png

I have an app that creates and releases millions of NSNumbers during the course of a very long calculation. I'm using @autoreleasepools with the various loops that create and process these objects. As you can see by the instruments screen cap, I have only 10656 living CFNumbers, yet at this point the app crashes due to unable to allocate memory for further objects. There are over 57 million CFNumbers in "transitory" state. Does this mean they are released? Why doesn't malloc use these memory locations yet?

I've included a mockup of the code I'm using. Run the testProbabilities method and it will count every 10000 iterations. I'm getting up to 790000 iterations with this code before an alloc error. Yet Instruments looks good to me.

@implementation MMTest

NSMutableSet *predictedSet;


-(NSMutableArray *)calculateCDF {

//// NORMALIZE PROBABILITIES ////

float probabilitySum = 0;
float runningCDFTotal = 0;
float normaledProbability = 0;

@autoreleasepool {

    NSMutableArray *CDF = [[NSMutableArray alloc] init];

    probabilitySum = 4.5; ////This is just a placholder for additive iteration of an array which creates this sum.

    //// CREATE CDF ////

    int x;

    for (x=0; x<50; x++) {

        normaledProbability = .2/probabilitySum;////This is placeholder for calculation on same array as above.
        runningCDFTotal += normaledProbability;

        [CDF addObject:[NSNumber numberWithFloat:runningCDFTotal]];

    }

    return CDF;

}

}

-(NSMutableSet *)generateNumbers {

@autoreleasepool {

    int x;
    double r = 0;

    if (!predictedSet) {
        predictedSet = [[NSMutableSet alloc] init];
    }

    else {
        [predictedSet removeAllObjects];
    }

    for (x=0; x<5;) {

        r = arc4random_uniform(1000)/1000;////I'm actually using SFMT here instead.
        int num = 0;
        __weak id CDF = [self calculateCDF];


        if (r <= [[CDF objectAtIndex:[CDF count]-1] floatValue]) {

            for (NSNumber *cdf in CDF) {

                if (r <= [cdf floatValue]) {

                    num = [CDF indexOfObject:cdf]+1;

                    [predictedSet addObject:[NSNumber numberWithInt:num]];

                    x++;

                    break;


                }

            }

        }

    }

    return predictedSet;

}

}

-(void)testProbability {

int x = 0;
BOOL nextSetFound = NO;
NSSet *nextSet = [[NSSet alloc] initWithObjects:
                  [NSNumber numberWithInt:1],
                  [NSNumber numberWithInt:2],
                  [NSNumber numberWithInt:3],
                  [NSNumber numberWithInt:4],
                  [NSNumber numberWithInt:5],
                  nil];

while (nextSetFound == NO) {

    @autoreleasepool {

        __weak id newSet = [self generateNumbers];

        x++;

        if ([nextSet isSubsetOfSet:newSet]) {

            nextSetFound = YES;
            NSLog(@"%@", newSet);

        }   

        if (fmod (x,10000) == 0) {
            NSLog(@"%i", x);

        }

    }

}

NSLog(@"%i", x);

}

@end

Upvotes: 0

Views: 295

Answers (2)

James Hunt
James Hunt

Reputation: 15

Not sure exactly why, but this exact code above is now working fine as is. In the course of testing various modifications, I enabled and disabled Guard Malloc and Malloc Stack Logging in Xcode. All of a sudden my modified versions began running with no crashes. I then tried this original version again and now it runs fine without crashing. It makes no sense, but I am just glad it is working.

@sergio: I would like to upvote your answer at least for providing much help and advice to optimize this code further, but I don't have a high enough reputation. Thank you for your help. I do appreciate it.

Upvotes: 0

sergio
sergio

Reputation: 69047

Am I wrong or you are not releasing anywhere CDF? This way, you are leaking CDF each time you call calculateCDF.

Try this:

id CDF = [self calculateCDF];

instead of

 __weak id CDF = [self calculateCDF];

and

id newSet = [self generateNumbers];

instead of

__weak id newSet = [self generateNumbers];

Specifying weak will make the object not owned by newSet and thus not released automatically by ARC.

On the other hand, Apple explicitly adives against using __weak for stack variables:

Take care when using __weak variables on the stack. Consider the following example:

       NSString * __weak string = [[NSString alloc] initWithFormat:@"First Name: %@", [self firstName]];
       NSLog(@"string: %@", string);

Although string is used after the initial assignment, there is no other strong reference to the string object at the time of assignment; it is therefore immediately deallocated. The log statement shows that string has a null value. (You will not see the effect if you use, for example, NSString * __weak string = [[NSString alloc] initWithString:@"First Name"]. In this case, initWithString: simply returns the string constant which is never deallocated.)

As I understand this paragraph, you either have an immediate deallocation, or a memory leak.

I tried your code on an iOS 4.3 iPhone (removing the __weak) and it shows a flat "physical free memory" under instruments.

OLD ANSWER:

The tricky thing with autorelease is that autorelease pools are drained at unpredictable times (usually when the app execute the run loop). This is different to using release in that it adds some delay between the moment when you are done with an object and the moment when the memory gets freed.

If you multiply this small delay (like an inertia the autorelease system has) by the number of autoreleased objects this could lead to peaks of memory usage. If iOS checks memory usage during one of those peaks, and your app cannot react properly to the memory warning that ensues, then you app is killed.

On the other hand, if you allocate a release pool, and it becomes huge within the same step of the run cycle, this could also lead to excessive memory usage.

You provide little detail about your code, so I cannot be more precise; in any case, I would try and explicit release objects, if possible. This would get rid of the two issues above. Or you could try and handle smaller autorelease pools (and you would fix the second kind issue)...

I would also suggest you to add a Memory Monitor to Instruments, so you will see how fast the system frees up memory.

PS: Now, what strikes me in the picture you attached is that the allocation diagram is absolutely flat: no allocations, no deallocations. I would expect the memory to go up and down (this is the sign of a healthy iOS app, in my experience).

Upvotes: 1

Related Questions