I have a SQLite backed database of roughly 4000 rows, connected to Core Data. The model is a simple overview -> detail model. Each overview (with title and subtitle) has a relationship to a detail, which contains detailed information. To view this data I have implemented a UITableView with a NSFetchedRequestController.
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"registered_name" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:sort];
NSPredicate *filterPredicate = nil;
NSString *sectionName = nil;
NSString *cacheName = nil;
NSFetchedResultsController *aFetchedResultsController = nil;
aFetchedResultsController.delegate = self;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setFetchBatchSize:20];
[fetchRequest setSortDescriptors:sortDescriptors];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Info" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:filterPredicate];
aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:sectionName cacheName:cacheName];
NSError *error = nil;
if (![aFetchedResultsController performFetch:&error])
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
[fetchRequest release];
[sort release];
return aFetchedResultsController;`
I have implemented everything and it works, but it works slowly. Specifically I’m concerned with the application launch.
This is the query that slows things down on launch time (SQL debug flag in XCode):
CoreData: sql: SELECT 0, t0.Z_PK FROM ZINFO t0 ORDER BY t0.ZREGISTERED_NAME
CoreData: annotation: sql connection fetch time: 0.8773s
CoreData: annotation: total fetch execution time: 0.8976s for 4302 rows.
This happens immediately after performFetch is executed. Why does Core Data feel the need to get and sort all 4302 rows of my database? If I set the setLimit to a small value, say 20, of course everything works extremely fast, but I only get 20 results displayed. On the other hand, I now have setBatchSize, which works great, and I can see the SELECT statements in the debug console, as I scroll the table view. But it still sorts 4302 rows at launch, which I feel is unnecessarily slowing down app launch time.
I intend to implement a normalized string sorting and a caseInsensitiveNumericCompare selector in NSSortDescriptor, as per Apple’s WWDC 2010 sample code, but it seems to me I’m missing something simple here.
Update:
Seems that I can’t use a custom selector caseInsensitiveNumericCompare: with NSSortDescriptor, since I am using SQLite as the database. I really don’t see what more I can do, to speed this up. Maybe SQLite will sort faster with shortened and normalized string, this is what I’m trying next.
Update 2:
With normalized string (no other letters or symbols except A-Z and 0-9), the launch time is down to about 0.7s. Not a big reduction. The last thing I’m trying is presorting the database and then assigning incremental ids to the rows. In NSSortDescriptor I will then sort by this numerical id. According to my SQL tests, it should be roughly 7x faster.
I solved my own problem. This is just a reminder for anyone who might run into similar issues. The error I made was not creating indexes for the required fields on the database side. I just clicked the “indexed” option in my model in XCode. Apparently this DOES NOT create any indexes, if you have supplied the .sqlite file yourself.
After a curious launch case, where Core Data decided to create the database for me, I saw these SQL sentences executed:
After I replicated these indexes on my own database, things have sped up considerably:
I’m extremely happy with these numbers. But since I’ve now presorted my database, I can use a number for NSSortDescriptor. This brings me down to:
So a little under 9 hundredths of a second, down from 9 tenths of a second.