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


