3

I am using ARC for my iOS project and am using a library called SSKeychain to access/save items to the keychain. I expect my app to access keychain items once every 10 seconds or so (to access API security token) at peak load and as such I wanted to test this library to see how it handles when called frequently. I made this loop to simulate an insane amount of calls and noticed that it bleeds a significant amount (~75 mb) of memory when run on an iPhone (not simulator):

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    dispatch_async(dispatch_get_main_queue(), ^{
        NSUInteger beginMemory = available_memory();
        for (int i = 0; i < 10000; ++i) {

            @autoreleasepool{
                NSError *  error2 = nil;
                SSKeychainQuery*  query2 = [[SSKeychainQuery alloc] init];
                query2.service = @"Eko";
                query2.account = @"loginPINForAccountID-2";
                query2.password = nil;
                [query2 fetch:&error2];
            }
        }
        NSUInteger endMemory = available_memory();

        NSLog(@"Started with %u, ended with %u, used %u", beginMemory, endMemory, endMemory-beginMemory);
    });

    return YES;
}

static NSUInteger available_memory(void) {
    // Requires #import <mach/mach.h>
    NSUInteger result = 0;
    struct task_basic_info info;
    mach_msg_type_number_t size = sizeof(info);
    if (task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size) == KERN_SUCCESS) {
        result = info.resident_size;
    }
    return result;
}

I am using SSKeychain which can be found here. This test bleeds about ~75 mb of memory regardless if things are actually stored on the keychain.

Any ideas what is happening? Is my testing methodology flawed?

  • 1
    You should use the leaks instrument to analyse your problem. Your test looks flawed to me because you are reporting memory use in the same method where you are consuming the memory so it is unlikely that the pool has drained when you report use. – Paulw11 Oct 22 '15 at 19:38
  • Ok, interesting. How would you recommend I check later? If I let this code run and then wait ~20 minutes after it is done executing the memory shown in the memory report on xcode still sits at the same value. – Master Crouch Oct 22 '15 at 19:46
  • Use instruments leak tool. Don't try and roll your own – Paulw11 Oct 22 '15 at 20:14
  • Ok. I used the leaks tool and found no leaks. Just a bunch of memory allocations that are not getting released. Hmm... Not clear what is happening here. – Master Crouch Oct 22 '15 at 20:16
  • So you can look at those allocations, look at where they were allocated and try and work out why they are retained – Paulw11 Oct 22 '15 at 20:19
  • I am not good with instruments. I am going to take a deep dive now and try to learn more about how to use them. I would love for someone to try to take a stab at this for me and I would accept that answer. – Master Crouch Oct 22 '15 at 20:36

2 Answers2

2

I ran your code under the Leaks Instrument and this is what I saw from the Allocations track -

Allocations Track

Which is what you would expect - a lot of memory allocated during the loop and then it is released.

Looking at the detail you see -

Detail

Persistent bytes on the heap of 2.36MB - This is the memory actually used by the app 'now' (i.e. after the loop with the app 'idling')

Persistent objects of 8,646 - again, the number of objects allocated "now".

Transient objects 663,288 - The total number of objects that have been created on the heap over the application lifetime. You can see from the difference between transient and persistent that most have been released.

Total bytes of 58.70MB - This is the total amount of memory that has been allocated during execution. Not the total of memory in use, but the total of the amounts that have been allocated regardless of whether or not those allocations have been subsequently freed.

The difference between the light and dark pink bar also shows the difference between the current 'active' memory use and the total use.

You can also see from the Leak Checks track that there are no leaks detected.

So, in summary, your code use a lot of transient memory as you would expect from a tight loop, but you wouldn't see this memory use in the normal course of your application execution where the keychain was accessed a few times every second or minute or whatever.

Now, I would imagine that having gone to the effort of growing the heap to support all of those objects, iOS isn't going to release that now freed heap memory back to the system straight away; it is possible that your app may need a large heap space again later, which is why your code reports that a lot of memory is in use and why you should be wary of trying to build your own instrumentation rather than using the tools available.

Paulw11
  • 108,386
  • 14
  • 159
  • 186
  • Awesome! Thanks for the awesome input. Let's wrap this up then with some final important questions. First, why is the memory report in xCode showing 86 mb used when it clearly contradicts the same run of tests in instruments? Second, I get essentially the same result as you when I use the leaks instrument BUT if I run the app on my iPhone through xCode (no instruments), click memory report, click "profile in instruments" in the memory report, and then click "restart" the allocations instrument shows me as having ~87.77 persistent mb / 145.58 total mb. – Master Crouch Oct 22 '15 at 22:45
1

You should use Instruments to figure out where/what is causing a leak. Its a very good tool to know how to use.

enter image description here

This article is a little dated but you should get the basic gist.

Ray Wenderlich - Instruments

Going off of Paulw11's comment I stumbled across this,

From NSAutoreleasePool Class Reference:

The Application Kit creates an autorelease pool on the main thread at the beginning of every cycle of the event loop, and drains it at the end, thereby releasing any autoreleased objects generated while processing an event.

So when you check it with instruments make sure the event loop has had time to finish. Maybe all you need to do is let the program keep running and then pause the debugger and check instruments again.

anders
  • 4,168
  • 2
  • 23
  • 31
  • This is where I started. There are no leaks. There is something that I am doing that is fundamentally wrong. – Master Crouch Oct 22 '15 at 19:44
  • To be clear, you think I should run the code, wait for it to execute, and then let some time pass to allow the memory to release? I have tried waiting for ~20 minutes after execution completed and saw no change in the amount of allocated memory. – Master Crouch Oct 22 '15 at 20:11
  • Update: Tried playing with allocations instrument for quite some time, it doesn't always show the same result as the memory report in xcode. Very weird. – Master Crouch Oct 22 '15 at 22:00