Monday, June 24, 2024

SwiftData vs. Realm Performance Comparison

Jacob Bartlett:

The Realm DB engine was written from the ground-up in C++ to minimise this overhead. […] Therefore, it’s not unreasonable to describe SwiftData as a wrapper over a wrapper over a wrapper.


These show that the SwiftData objects took around 10x longer to instantiate.


Realm topped out at writing 2,000,000 simple User objects before hitting an out-of-memory exception and crashing. SwiftData was only able to manage a paltry 1,000,000 objects. Realm was about 3-6x faster beyond write volumes exceededing 1,000 objects.


For our dead-simple User objects (a few fields and no relationships), we queried for all users with the first name “Jane”. Realm was much faster, its zero-copy architecture shining when reading data directly into memory. For simple SwiftData objects, read performance started off okay and degraded sharply with over 100k objects in the database.

With our more complex Student model, we searched for all Physics students who got the top grade. We observed the opposite effect: SwiftData was usually more than 10x faster than Realm.

Interestingly, SwiftData sometimes has the edge with smaller datasets, both in terms of RAM use and speed.


Update (2024-06-26): Wade Tregaskis:

I think we (Shark engineers) tried to be open-minded and kind. We were sceptical, but you never know until you actually look. We could see some potential for a more general query capability, for example. But of course the first and most obvious hurdle was: how well does Core Data handle sizeable numbers of records? Oh yes, was the response, it’s great even with tens of thousands of records.


We asked how it did with tens of millions of records, and that was pretty much the end of the conversation.


I guess by modern standards SQLite is considered efficient and fast, but – hah – back in my day SQLite was what you used when you didn’t have time to write your own, efficient and fast persistent data management system.

Helge Heß:

The Realm vs SwiftData thing encouraged me to try the same project w/ Lighter. It isn’t exactly the same as Lighter is no ORM, but it will give some hints on what a little lower level can yield. For now, the 10k plain items test:

💽 User instantiation: 0.0676
💽 Create users: 1.9151

💽 User instantiation: 0.0229
💽 Create users: 0.1220

💽 User instantiation: 0.0049
💽 Create users: 0.0820


Something disappointing in SwiftData is that it doesn’t make use of the static nature of the macro(s). The macro can’t see the full schema like Lighter does, but it could still statically generate a ton, e.g. a static snapshot struct for the backing data. Or predefined indices for quickly binding the snapshot to the SQLite API (or really any).

Instead we get custom backends.

Helge Heß:

So I’ve essentially ported the whole perf test over to Lighter, which was interesting because it also demonstrates some key differences in the approaches. E.g. to update the math grades of the bad students, the sample essentially loads all students and their grades into memory, then updates the grades one-by-one and saves them back to the store.

In plain SQL that would be just a single line UPDATE statement, no loading at all.

The SwiftData implementation in the test also seems to be not quite optimal. E.g. to update the items, already fetched items get inserted into a new ModelContext and then saved (a lot of new MCs are created, which seems completely counter the idea, though sometimes necessary to keep SwiftData RAM at bounds). Presumably just saving the context used to fetch the items would be quite a bit more efficient.

Update (2024-07-01): Aleksandar Vacić (Mastodon):

Thus by using somewhat reasonable but still sizable chunks (100k records) of the original data set and employing Core Data best practices, we lowered peak memory usage 10× and shortened total time spent about 2.5× which is no small feat.

4 Comments RSS · Twitter · Mastodon

One problem that CoreData and SwiftData solve that should not be underestimated is its integration in the UI. The ease of use that NSFetchedResultsController brings to using tables and other features just safes so much time...

@Karsten I guess, nowadays, with UITableViewDiffableDataSource, the advantage is less needed, at least for inexperienced developers. I don’t think anyone with an explicit performance requirement would use a diffable data source, especially with thousands of items updating, but for smaller projects, it does solve these issues.

>I don’t think anyone with an explicit performance requirement would use a diffable data source, especially with thousands of items updating, but for smaller projects, it does solve these issues.

Yea. Diffable datasources make it difficult to swap in placeholder rows for offscreen items. Even if you can unload a bunch of offscreen items until the user scrolls them on screen... if you use a diffable datasource you still have to allocate a unique object for each row.

I applaud Realm work, but I had to stop using it for my iOS app, Weathergraph, after I realized Realm alone (with extremely simple data model) takes 15 MB of RAM out of 30 MB widget limit, causing the widget extension to be kild on OOM.

If your app plans to use widgets, it is worth forming a plan on how to display the data without needing Realm.

In my case, I migrated to a simple Protobuf store, and some logic to save data when changed, and load it when the widgetkit extension needs to update the widget.

Leave a Comment