CoreData Performance Mystery

  • Hello all -

    I decided to take the plunge and write a CoreData app.  I've been
    having a pretty good time of it, but have recently come across a
    performance issue that has me stumped.

    I have two pretty basic entities, Genre and Book.  You can imagine
    the Genre entity has a to-many Book relationship and the associated
    inverse.  My test app has 2 table views, one with a Genre list and
    the other containing the Books for the currently selected Genre.
    I've implemented full drag and drop functionality so that books can
    be moved or copied from one Genre to another.  Functionally this
    works perfectly.  Performance wise it's fairly abysmal.  Changing 200
    books from one Genre to another takes roughly 4 seconds.  This seems
    excessive?  Basically my code for changing genres is as such:

    // get genre based on the genre table row being dragged to
    NSManagedObject *genreType;

    // the array of book objects being dragged
    NSArray *draggedBooks;

    // this is literally all there is to the loop
    NSEnumerator *enumerator;
    enumerator = [draggedBooks objectEnumerator];
    while (book = [enumerator nextObject]) {
        [book setValue:genreType forKey:@"genre"];
    }

    That above while loop appears to take nearly 1 second per 50 book
    objects.  ???  This is using an SQLite store.  Curiously selecting
    Undo from the Edit menu produces the inverse behavior in the blink of
    an eye.  That led me to believe that perhaps the performance was a
    result of building up all of the inverse operations in the app's
    undoManager.  So I set about setting the undoManager to nil and also
    disabling the undoManager using - disableUndoRegistration.  While
    both worked as expected they failed to change the performance of
    switching genres in any appreciable way.

    Is my approach to switching genres flawed or inefficient in some
    manner?  Am I missing something?  Again, *functionally* it appears to
    work perfectly.  It's just the performance that seems really poor.
    The only thing keeping me sane is I notice similar performance when
    moving a similar number of objects between collections in Bare Bones
    cool app Yojimbo.  Perhaps it's just the nature of the CoreData beast?

    Advice, insight or RTFM URL's would be much appreciated!

    Mike
  • On 15 Feb '08, at 8:26 PM, Mike McNamara wrote:

    > That above while loop appears to take nearly 1 second per 50 book
    > objects.  ???

    Any time something is too slow, my first response is to sample the
    app. You can use Activity Monitor to do this, or just type "sample
    MyAppName 2" in a shell. Even if most of the time is spent in some
    system framework, the symbol names are usually clues about what's
    going on.

    You can also use Shark if you want to get into more serious profiling,
    but sampling is really quick and usually helpful.

    —Jens
  • Mike,

    There are several previous discussions about using Shark,
    Instruments, and plain old SQL logging to identify performance
    problems using Core Data.  Try starting with
    <http://www.cocoabuilder.com/archive/message/cocoa/2008/2/6/198068>

    You should also review the Core Data Performance chapter in the Core
    Data Programming Guide at developer.apple.com.  I expect the sections
    on batch faulting and prefetching will resolve most of your issues.
    --

    -Ben
  • At 3:38 PM -0800 2/16/08, Ben Trumbull wrote:

    > There are several previous discussions about using Shark,
    > Instruments, and plain old SQL logging to identify performance
    > problems using Core Data.

    It doesn't appear as though my application is faulting in any way
    however.  In fact based on SQL logging, all of the SQL fetch related
    activity taking place at great velocity.  I have in fact been able to
    cut the time required to move my Book objects from one genre to
    another by using an NSSet vs. looping through the books:

    // get genre based on the genre table row being dragged to
    NSManagedObject *genreType;

    // the array of book objects being dragged
    NSArray *draggedBooks;

    // looping through draggedBooks using an enumerator and changing
    // the genre book by book is slow -- create an NSSet instead
    NSSet *draggedSet = [NSSet setWithArray:draggedBooks];

    // change the genre for the set
    [draggedSet setValue:genreType forKey:@"genre"];

    This cuts the move time in about half.  So moving 200 books objects
    now takes about 1.5 second vs. the 3 - 4 it was previously.  Using
    Instruments it appears to me that this 1.5 second period is almost
    entirely related to KVO stuff like didChangeValueForKey.  That seems
    par for the course given that 200 relationships and inverse
    relationships are changing buy I'm still somewhat surprised by how
    long this takes?  So I suppose it's more of a KVO performance issue
    than a CoreData one.

    Thank you Ben and Jens for your suggestions!

    Mike
  • > This cuts the move time in about half.  So moving 200 books objects
    > now takes about 1.5 second vs. the 3 - 4 it was previously.  Using
    > Instruments it appears to me that this 1.5 second period is almost
    > entirely related to KVO stuff like didChangeValueForKey.  That seems
    > par for the course given that 200 relationships and inverse
    > relationships are changing buy I'm still somewhat surprised by how
    > long this takes?  So I suppose it's more of a KVO performance issue
    > than a CoreData one.

    Mike,

    Please please take a Shark sample (with embedded source, at 100us
    samples) and file a bug with bugreport.apple.com so we can improve
    things.
    --

    -Ben