adamek
adamek

Reputation: 2674

Modern Core Data Performance with UUID

I have a Core Data application with:

Each entity has a uuidKey string attribute with a UUID string imported from an online database.

I'm trying to run the app on an iPhone 5S. Inserting objects is relatively fast, but finding and linking the relationships is longer than anticipated.

let debugTime = NSDate()
let thisRequest = NSFetchRequest(entityName: "Ledgers")
let keyLeft = NSExpression(forKeyPath: "uuidKey")
let keyRight = NSExpression(forConstantValue: ledgerKey)
let keyPredicate = NSComparisonPredicate(leftExpression: keyLeft, rightExpression: keyRight, modifier: NSComparisonPredicateModifier.DirectPredicateModifier, type: NSPredicateOperatorType.EqualToPredicateOperatorType, options: NSComparisonPredicateOptions.allZeros)
thisRequest.predicate = keyPredicate
thisRequest.fetchBatchSize = 1
thisRequest.fetchLimit = 1
println("DEBUG COMMENT: \(debugTime.timeIntervalSinceNow) time to create thisRequest.")
var keyError: NSError? = nil
if let keyArray = self.managedObjectContext!.executeFetchRequest(thisRequest, error: &keyError) {
    if keyArray.isEmpty {
        myLedger = nil
    } else {
        if let thisLedger = keyArray[0] as? Ledgers {
            println("DEBUG COMMENT: \(debugTime.timeIntervalSinceNow) time to find thisLedger.")
            myLedger = thisLedger
        } else {
            myLedger = nil
        }
    }
}
println("DEBUG COMMENT: \(debugTime.timeIntervalSinceNow) time to link myLedger.")

I get log times like this:

DEBUG COMMENT: -9.20295715332031e-05 time to create thisRequest.
DEBUG COMMENT: -0.174275994300842 time to find thisLedger.
DEBUG COMMENT: -0.174659013748169 time to link myLedger.
DEBUG COMMENT: -0.00464397668838501 time to find and link myAccount.

0.092 milliseconds to create the NSFetchRequest is fine. 174 milliseconds to find the Ledger? On an indexed UUID string when there are 24k Ledgers?!? When I need to link this to 57k Journals?

Faster is always better. 4 milliseconds to find a link myAccount when there are 549 accounts is slightly disappointing but acceptable. For the Ledger search I was expecting about 25-50 milliseconds, not 174!

I'm running this in its own core data store and context:

    // Initialize the persistent store coordinator for the sync session.
    let url = appDel.applicationDocumentsDirectory.URLByAppendingPathComponent("FoodyCoreModel.sqlite")
    self.syncStore = NSPersistentStoreCoordinator(managedObjectModel: appDel.managedObjectModel)
    var error: NSError? = nil
    var failureReason = "There was an error creating or loading the application's saved data."
    let storeOptions: [NSObject: AnyObject] = [NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true]
    self.syncStore.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: storeOptions, error: &error)

    // Initialize the managed object contextfor the sync session
    self.syncContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType)
    self.syncContext.persistentStoreCoordinator = self.syncStore
    let nmp = NSMergePolicy(mergeType: NSMergePolicyType.MergeByPropertyStoreTrumpMergePolicyType)
    self.syncContext.mergePolicy = nmp

Is this reasonable performance to expect with an iPhone 5S? I've read many of the Core Data performance questions here, but many of them date from years ago. I was hoping that iOS 8 and more recent devices would be significantly faster. I'm not trying to do a MATCHES or CONTAINS search, just EQUALS on an indexed text field!

I've read the Core Data Performance Guide. Is there something I'm missing or am I just expecting too much from a mobile device?

Upvotes: 4

Views: 1574

Answers (1)

Tom Harrington
Tom Harrington

Reputation: 70976

The most likely slowdown in your code is that you're fetching Ledger instances one at a time, and you say there are 24,000 of them. That's going to be a major bottleneck no matter what. Fetching is a relatively expensive operation, regardless of whether you're using a string ID in the predicate, so your code is going to be spending most of its time fetching.

You should be fetching in batches instead of one at a time. Collect 50 or 100 or so uuidKey values into an array and use a predicate format like "uuidKey in %@" with the array as the substitution variable. Process that batch, and repeat until done.

For more detail on how to import data efficiently, refer to Apple's "Efficiently Importing Data" guide, especially the section called "Implementing Find-or-Create Efficiently".

As an aside, using println like this is a really poor way to measure performance. Use Instruments. It'll give you more detail with less overhead and without cluttering up your code, and will help you nail down exactly where bottlenecks are.

Upvotes: 5

Related Questions