简体   繁体   中英

iOS CPU usage skyrockets here

I have an issue with my app where it spikes up to over 100% CPU usage when gathering data. I have done everything I can think of to reign in the processing so that it doesn't lock the device up, and still continues to get the data that's needed.

Our customers have some big databases, so downloading the entire database is out the question. We are also using REMObjects as middleware, so I have to work with that. What I did was figure out what data was needed for the user, and setup a series of calls to gather that information. I think that where my problem lies is in the fact that the database can only process up to 1500 items in a call.

Here is a sample query string that is getting sent to the server.

SELECT COMMUNICATIONID, PHONEEXTENTION, PHONEDESC, PHONETIMETOCALL, PHONENUMBER 
FROM PHONE WHERE COMMUNICATIONID IN (3761, 3793, 5530, 4957, 4320, 1914, 3715, 6199, 5548,
5580, 5994, 5867, 1437, 4943, 6217, 3765, 2442, 227, 4084, 977, 6822, 5680, 263, 4502, 
327, 6112, 136, 7053, 5571, 6958, 6799, 5525, 6530, 4779, 604, 2182, 6198, 3792, 6071, 
4383, 5866, 7444, 1309, 226, 4083, 5916, 1295, 626, 1249, 1950, 2141, 3369, 326, 135, 
6780, 5411, 5938, 4424, 6034, 649, 6179, 5861, 4778, 5479, 2181, 6197, 3791, 5815, 6070, 
6420, 7935, 4542, 4319, 6679, 4942, 4082, 4974, 5533, 5788, 5597, 976, 3764, 1917, 6202, 
134, 6779, 3768, 5410, 5665, 7880, 7052, 6033, 5492, 6815, 3118, 4218, 5110, 6529, 6115, 
6069, 348, 4318, 4382, 1498, 6406, 4941, 7443, 2376, 4623, 5755, 5532, 6201, 6392, 625, 
7270, 4977, 6396, 6524, 5664, 7051, 725, 6032, 6701, 6160, 5491, 5937, 6273, 1875, 6114, 
5477, 6528, 5573, 4936, 6705, 2180, 3758, 5527, 5368, 5814, 7328, 7424, 429, 5991, 1434, 
6391, 6200, 7283, 5868, 5900, 228, 4085, 6109, 1106, 5791, 692, 6095, 7210, 2893, 1188, 
6814, 4217, 5572, 3757, 5813, 3694, 796, 605, 6486, 128, 4144, 5722, 5754, 1915, 5676, 
5549, 5581, 4976, 5917, 5822, 2174, 6158, 1633, 4566, 5267, 4885, 4503, 1874, 6113, 5476, 
4425, 4871, 5526, 6531, 7886, 1496, 5194, 127, 4780, 5721)

That string is created by the following method, which then sends it asynchronously to the server. There is one issue in this method that I know is a problem, but I haven't had the time to circle back to it and devise a better solution. I am using respondsToSelector and performSelector to process additional methods based on the table that we're gathering details from.

- (void)processRequest
{
    if( requestQueue.count == 0 )
        return;

    if( processingQueue.count > 3 )
        return;

    Request *request = requestQueue[0];
    [requestQueue removeObjectAtIndex:0];
    DADataTable *source = request.source;
    NSString *destTableName = request.destTableName;
    NSString *sourceKey = request.sourceKey;
    NSString *query = request.query;
    NSArray *destKeys = request.destKeys;
    NSString *originMethodName = request.originMethodName;
    NSArray *destinationMethods = request.destinationMethods;
    NSString *message = request.loadingMessage;

    [[NSNotificationCenter defaultCenter] postNotificationName:@"GATHERINGDATA" object:nil];


    // Cycle through the rows in the source table and extract the keys we need.
    // originMethodName is needed because some tables require additional checks
    // to determine what kind of key we are working with
    // sourceKey is the string that holds the key we're looking for, which is
    // used on tables that don't need specific filtering
    NSSet *set = [self getSourceSet:source originMethodName:originMethodName sourceKey:sourceKey];

    // getLists takes the set generated by getSourceSet and converts the list of
    // ids into a comma separated list of items suitable for passing into a query
    // Currently there is a 1400 item limit per list to keep from exceeding the server
    // limit, which is currently 1500
    NSArray *lists = [self getLists:set];

    NSString *msg = @"Loading Data";
    NSLog(@"%@", message);
    for( NSString *tList in lists ) {
        if( tList.length == 0 ) {
             NSLog(@"%@ not needed", originMethodName);
             continue;
        }

        query = [query stringByAppendingFormat:@"%@)", tList];

        NSLog(@"%@: %@", destTableName, query);
        DAAsyncRequest __block *r = [fda beginGetDataTableWithSQL:query withBlock:^(DADataTable *table){
             DADataTable *destination = [tables valueForKey:destTableName];
             if( tables.count == 0 ) destination = table;
             else if( [destination rowCount] > 0 )
                //dispatch_async(queue, ^(){
                [destination mergeTable:table withPrimaryKeys:destKeys];
                //});

             else
                destination = table;

             [[NSUserDefaults standardUserDefaults] setValue:msg forKey:@"LoadingMessage"];
             [[NSNotificationCenter defaultCenter] postNotificationName:InitialViewControllerNotificationLoadingUpdate object:nil];
             [[NSNotificationCenter defaultCenter] postNotificationName:@"UpdateStatus" object:nil];

             //dispatch_async(queue, ^(){
             [tables setValue:destination forKey:destTableName];
             //});

             for( NSString *method in destinationMethods) {
                SEL tMethod = NSSelectorFromString(method);
                if( [self respondsToSelector:tMethod]) {
                    [self performSelector:tMethod withObject:table];
                }
             }


             if( [self busy] &&
                 [[source name] isEqualToString:DataAccessTableCustomer])
             {
                [[NSUserDefaults standardUserDefaults] setValue:nil forKey:@"FETCHINGJOB"];
                [[NSNotificationCenter defaultCenter] postNotificationName:@"JOBFETCHED" object:nil];
             }
             if( [[[NSUserDefaults standardUserDefaults] valueForKey:@"FETCHINGCONTACT"] isEqualToString:@"YES"] &&
                ([[source name] isEqualToString:DataAccessTablePerson] ||
                 [[source name] isEqualToString:DataAccessTableOrganization] ||
                 [[source name] isEqualToString:DataAccessTableOrganizationAddress]))
            {
                 [[NSUserDefaults standardUserDefaults] setValue:nil forKey:@"FETCHINGCONTACT"];
                 [[NSNotificationCenter defaultCenter] postNotificationName:@"CONTACTFETCHED" object:nil];
            }
            [processingQueue removeObject:r];
            [[NSNotificationCenter defaultCenter] postNotificationName:@"PROCESSREQUEST" object:nil];
        }];
        [processingQueue addObject:r];
    }
}

Any help here will be greatly appreciated! Thanks for taking the time to look.

Yes. Basically the golden rule is: do not optimize prematurely .

But anyhow. My first guess would be: replace NSString query with NSMutableString query . Because you are creating 1500 NSString objects on the heap with always increasing length, just to throw them away in the next loop. An NSMutalbeString keeps up the memory for a much longer time when appending - and you are always talking to the same object. '(Then use appendFormat without the re-assignment instead of stringByAppendingFormat with assignment!)

Look here: Is NSMutableString's -appendString: method an efficient way to build up a large string?

Having your CPU climb to over 100% is not necessarily a bad thing. On a device with multiple cores (iPhone 4s and later) the CPU utilization is 100% times the number of cores. So on a 4s max is 200%, and on a 5s it's 400%.

Doing a bunch of processing in a loop maximizes CPU usage on that thread, since the code runs at full speed until it's done. That's normal and appropriate.

Are you seeing laggy UI performance? That's what you should use as your gauge that you need to improve things.

My suggestion would be to rewrite your code to run on a GCD background queue. First try default priority (the same priority as the UI.) On a multi-core machine, that will tie up one of the cores. On an iPhone 4, though, it might make the UI laggy. In that case you could switch to the next-lower priority, which would make it take longer, but give a higher priority to the UI.

You could then optimize your code if needed. User defaults is not the most efficient way to handle state data in a loop. You might try removing the user defaults calls and switch to saving data in an instance variable or, in a data container singleton if you need to pass info between objects. Also NSNotificationCenter has more overhead than delegate calls, block calls, or simple method calls.

However, I would not worry about those things until you determine that optimization is needed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM