views:

129

answers:

3

Core data's performance on the iPhone is absolutely miserable. Is indexing completely broken or is it just a poor implementation?

I have about 21500 objects of a single type in my core data store (SQLite backing store). The objects are indexed on a UUID which is an NSString (for example, one looks like this: "6b09e200-07b6-11df-a245-002500a30d78").

A single fetch where the object exists using executeFetchRequest in NSManagedObjectContext takes about 0.75 seconds! This is with the simplest possible predicate of "uuid == $UUID" where $UUID is a string like the example above.

This is really surprising. If I wanted to fetch every object in my store, one by one, it would take nearly 4.5 hours!

Is there anyway to improve this performance or should I just abandon core data all together?

+2  A: 

This isn't going to answer your question but might give you something to think about. Using just SQLite on the iPhone I was mightily disappointed with performance. I was dealing with about 8000 entries which would take about a two minutes to insert / sort if returning all and so on.

Playing around with it I found the time needed to filter / sort in memory was 100 fold better than letting it be done SQLite and I think its mainly due to the performance of the flash memory.

In short the less Core Data has to use the flash memory the better performance you will get and I don't think there would be many ways to make it much better.

Rudiger
I've run into those problems too, and I've been able to get around most of them by doing as much work in memory as possible and then doing all the inserts at once. Also, I hold off on saving the core data until as late as possible so it seems to be much more efficient to do it all at once at the end rather than save more frequently. The one thing I can not get around is the fetch performance.
Mike
+1  A: 

Several points. If it takes 5 seconds to fetch 21,500 rows, it sounds like you're running on an older device. Like a 3G or original iPhone. The memory and I/O performance on those is just plain slow. You'll need to handle your data with extreme care to avoid reading it all into memory and doing unnecessary I/O. You may find -setFetchBatchSize particularly useful. If you're running on a 3GS, 10-20 thousand rows is manageable but will require care. If you're on an ipad or iphone4, this shouldn't be much of an issue.

You don't need to create your own UUID, except to interface with an external system, like a server. Each Managed Object has an objectID which is an OOP representation of its primary key. Just pass the objectID around, and do queries like @"self = %@" or @"self IN %@" to search for an object by its ID or an array of ids. You can also use -existingObjectWithID:error: to look up just 1 object by its objectID which will be faster than a generic fetch request with a generic predicate.

The best way to verify the index is being used as you expect is to run the app in the simulator with the executable argument

-com.apple.CoreData.SQLDebug 1

that will log to console the SQL being generated. You should see some stuff ending in something like t0.uuid == ?

You can take that SQL select statement, and run it through SQLite's explain query facility. Run /usr/bin/sqlite3 against the db file in the simulator. Do

.explain ON explain query plan copythatsqllinehere

it should print out something like 0|0|TABLE ZFOO AS t0 WITH INDEX something

if it's missing "with index" then you have some issue with either the way you created the Core Data store (are you sure the model is marked to index uuid ?) or there's something else with your fetch request.

This is really surprising. If I wanted to fetch every object in my store, one by one, it >would take nearly 4.5 hours!

I suppose you could do it that way, as one of the most painful ways possible. Or you could use -setFetchBatchSize: and iterate over batches of the objects very quickly.

Also, keep in mind that each fetch does I/O with the database to remain in sync with what any other threads have saved. Fetching is not some magical dictionary look up. There's a lower bound on the time it takes to execute the smallest unit of I/O. You're going to want to amortize the number of individual I/O requests to get the best performance. You'll have to balance that against reading too much into memory at once.

If you continue to have trouble, please file a bug with bugreport.apple.com

  • Ben
Ben
Its not an older iPhone, its an 3GS. I've also tried it on a new iPhone 4, but the performance was not that much better. I create my own UUID for exactly that reason -- I need to interface with an external system that uses its own UUIDs to refer to data and I need to fetch based on those UUIDs.
Mike
I'll try your suggestions to examine the queries. I think that the indexes are not being used or not being used correctly. The fetch time seems to be roughly linear with the size of the table. If the table truly was indexed, it should be doing a binary search (or tree-based search) and it should be proportional to the log of the size of the table. If it is the case that my table isn't indexed, then it's likely a bug and I'll file it with apple.
Mike
The 4.5 hour remark was an example of how slow it is. Since I am fetching by individual ids, there is no way to fetch in batches, so setFetchBatchSize is useless. The exception is what am doing now, which is to fetch the whole table and build my own dictionary containing the UUIDs of each object.Fetching SHOULD be some magical dictionary lookup. At least in a modern database. The lower bound should not be in the second range for a medium-sized table. Even if the database does not keep table indexes cached in memory, the performance should not vary linearly with the size of the table.
Mike
It seems the indexes are fine. The problem might be that all of my objects inherit from a single abstract object. Apple seems to implement entity inheritance by sticking ALL of the data in a single table, so all of my data is in one huge table with nearly 100 columns!
Mike
A: 

Hi, the trick of using core data is that only data which are actually needed are fetched from store and held in memory. I can't imagine how would I edit/reorder/whatever 21500 rows on a device like an iPhone. There are several ways how to improve CoreData performance: - setFetchBatchSize - using primitive methods - loading only properties which are needed

I remember a WWDC video comparing SQLite & CoreData performance and CD was a clear winner.

BobC
Core Data can use a binary file, sqlite, or memory only as it's backing store on the iPhone. Obviously, core data using memory would be fastest but your data isn't actually saved to disk so it's not useful in most situations. SQLite as a backing store is faster than a flat binary file so core data can never be as fast as SQLite alone since it always must add some overhead. In fact, it seems like it's likely far slower.
Mike
I'm not sure how setFetchBatchSize would help me? I usually am only fetching individual items of data at a time. I'm not actually modifying any of that 20,000 row table. It's purely a pre-builty reference table that is necessary for my application. (Think of an english dictionary with 20,000 words in it, you never edit it but you want to be able to look up words quickly in it).
Mike