Creating instances of SwiftData Models very slow

I have a for loop where I iterate over the contents of a CSV file. With around 30k Lines with around 230k elements in total.

At the end of each iteration of the loop I create an instance of my Data model. Where I enter the parsed info from each line of the CSV. This takes around 90+ seconds to complete.

If I create an absolute identical copy of my Data model but omit the @Model macro. The loop takes 3 seconds.

There is no manual saving of the Model data and isAutosaveEnabled is set to false.
The only difference between to loops is one creates and instance of @Model Class and the other just a Class.

What is it with @Model that makes it so slow?
In previous version of my app using core data loop took 3 seconds also.

Using Core Data directly is probably always going to be faster (for well-written code) since it's skipping the layer of non-trivial abstraction that is SwiftData.

Are you testing with a release build (i.e. using -O or similar)? SwiftData uses structs which rely pretty heavily an optimisations - that aren't enabled for debug builds - for decent performance. Core Data uses Objective-C objects which in principle aren't as efficient (in basic method-call senses) but don't require any compiler optimisations to be at their best (and are very highly hand-tuned to be faster than you'd expect).

A Time Profile (via Instruments) will likely shed light on where the time is going, and that will hopefully lead you towards a solution (or at least a workaround).

1 Like

Following the thread. In the process of trying to migrate to SwiftData from CoreData; I find SwiftData is slower, takes more CPU %, and uses more memory. Profiling with the instrument showed that operations (e.g. Fetch/Query) that took around 1ms in CoreData take around 20ms now. 20ms doesn't seem much but it is 20 times slower, and when being called repeatedly, users start to feel the slow down.

1 Like

There is open source framework (I don't remember the exact name) which gives the ability to use both CoreData and SwiftData. In CoreData you can do batch operations which is faster than saving single models one by one.
Be careful, this framework rely on SwiftData internals that can change over time, but you can cover critical parts of your code with Tests.

Are you saving each model at the end of each loop iteration? Could you instead accumulate the created models and save them once at the end? Perhaps you could even create some lightweight structs that act as proxies for the SwiftData models, and then use those to create\save the models after the loop.

In any case, you're unlikely to get any Apple devs to respond to this in any meaningful way on this forum, as SwiftData is an internal Apple framework and not subject to language evolution. It may be worthwhile filing a feedback. If there's something slow about SwiftData models, it's likely related to implementation details unavailable to the public rather than anything general to macros.

1 Like

Also, tangentially, make sure you're familiar with some of SwiftData's pitfalls before you dive into it. It might not be all that suitable for your use case.

I know there's a perception - wishful thinking, perhaps? - that SwiftData is the new and "better", "Swiftier" version of Core Data. But I don't think that's a healthy way to look at it. It has some merits over Core Data, but also a lot of limitations and some flaws. Don't be afraid to just use Core Data directly if that's more appropriate for your needs. It's not deprecated; it's not going away in the foreseeable future.

There's also many other options in that same relational data abstraction space, e.g. Realm, that might be worth considering too.

3 Likes

No not making any save. I mean I intend to. I was placing all items in the loop into a holding array. Then outside the loop saving that array into swiftData. The save is instant when I do. However the loop creating the model objects is what takes all the time.

This loop through 30k+ lines of a CSV file takes 90s

@Model 
    class TimetableData {
     
        var arrivalTime : Int = 0
        var departureTime: Int = 0
        var departureRoute : Int = 0
        var directionOfTravel : Int = 0
        var dutyNumber : Int = 0
        var facilityIdentifier : String = ""
        var locationType : String = ""
        var mode : String = ""
        var nonStopStatus : Int = 0
        var recordIdentifier : String = ""
        var timetableIdentifier : Int = 0
        var trainNumber : String = ""
        var tripNumber : Int = 0
        var tripStartSite : String = ""
        var uniqueLocationCode : String = ""
        
        init(arrivalTime: Int = 0, departureTime: Int = 0, departureRoute: Int = 0, directionOfTravel: Int = 0, dutyNumber: Int = 0, facilityIdentifier: String = "", locationType: String = "", mode: String = "", nonStopStatus: Int = 0, recordIdentifier: String = "", timetableIdentifier: Int = 0, trainNumber: String = "", tripNumber: Int = 0, tripStartSite: String = "", uniqueLocationCode: String = "") {
        
            self.name = name
            self.arrivalTime = arrivalTime
            self.departureTime = departureTime
            self.departureRoute = departureRoute
            self.directionOfTravel = directionOfTravel
            self.dutyNumber = dutyNumber
            self.facilityIdentifier = facilityIdentifier
            self.locationType = locationType
            self.mode = mode
            self.nonStopStatus = nonStopStatus
            self.recordIdentifier = recordIdentifier
            self.timetableIdentifier = timetableIdentifier
            self.trainNumber = trainNumber
            self.tripNumber = tripNumber
            self.tripStartSite = tripStartSite
            self.uniqueLocationCode = uniqueLocationCode
        }
        
    }


for line in CSVFile {  

var columnValue = line.components(separatedBy: ",")

let timeTableDataEntry  = TimetableData (
                     
                            arrivalTime: Int(columnValue[0])!,
                            departureTime: Int(columnValue[1])!,
                            departureRoute: Int(columnValue[2])!,
                            directionOfTravel: Int(columnValue[3])!,
                            dutyNumber: Int(columnValue[4])!,
                            facilityIdentifier: columnValue[5],
                            locationType: columnValue[6],
                            mode: columnValue[7],
                            nonStopStatus: Int(columnValue[8])!,
                            recordIdentifier:  columnValue[9],
                            timetableIdentifier: Int(columnValue[10])!,
                            trainNumber: columnValue[11],
                            tripNumber: Int(columnValue[12])!,
                            tripStartSite: columnValue[13],
                            uniqueLocationCode:columnValue[14]
                        )

}

This one takes 3 seconds. its only adding the @Model macro that makes it unusably slow

class TimetableDataForTesting {

        var arrivalTime : Int = 0
        var departureTime: Int = 0
        var departureRoute : Int = 0
        var directionOfTravel : Int = 0
        var dutyNumber : Int = 0
        var facilityIdentifier : String = ""
        var locationType : String = ""
        var mode : String = ""
        var nonStopStatus : Int = 0
        var recordIdentifier : String = ""
        var timetableIdentifier : Int = 0
        var trainNumber : String = ""
        var tripNumber : Int = 0
        var tripStartSite : String = ""
        var uniqueLocationCode : String = ""

        init(arrivalTime: Int = 0, departureTime: Int = 0, departureRoute: Int = 0, directionOfTravel: Int = 0, dutyNumber: Int = 0, facilityIdentifier: String = "", locationType: String = "", mode: String = "", nonStopStatus: Int = 0, recordIdentifier: String = "", timetableIdentifier: Int = 0, trainNumber: String = "", tripNumber: Int = 0, tripStartSite: String = "", uniqueLocationCode: String = "") {

            self.name = name
            self.arrivalTime = arrivalTime
            self.departureTime = departureTime
            self.departureRoute = departureRoute
            self.directionOfTravel = directionOfTravel
            self.dutyNumber = dutyNumber
            self.facilityIdentifier = facilityIdentifier
            self.locationType = locationType
            self.mode = mode
            self.nonStopStatus = nonStopStatus
            self.recordIdentifier = recordIdentifier
            self.timetableIdentifier = timetableIdentifier
            self.trainNumber = trainNumber
            self.tripNumber = tripNumber
            self.tripStartSite = tripStartSite
            self.uniqueLocationCode = uniqueLocationCode
        }

    }


for line in CSVFile {  

var columnValue = line.components(separatedBy: ",")

let timeTableDataEntry  = TimetableDataForTesting (

                            arrivalTime: Int(columnValue[0])!,
                            departureTime: Int(columnValue[1])!,
                            departureRoute: Int(columnValue[2])!,
                            directionOfTravel: Int(columnValue[3])!,
                            dutyNumber: Int(columnValue[4])!,
                            facilityIdentifier: columnValue[5],
                            locationType: columnValue[6],
                            mode: columnValue[7],
                            nonStopStatus: Int(columnValue[8])!,
                            recordIdentifier:  columnValue[9],
                            timetableIdentifier: Int(columnValue[10])!,
                            trainNumber: columnValue[11],
                            tripNumber: Int(columnValue[12])!,
                            tripStartSite: columnValue[13],
                            uniqueLocationCode:columnValue[14]
                        )

}

SwiftData's in-memory storage stores the data in a Dictionary, so being 30x slower than a struct to construct and initialize is not entirely shocking. There's probably room to optimize it and filing Feedbacks complaining about the performance may help get that prioritized, but that obviously doesn't help you now.

3 Likes

Going to use one of my developer code level support tickets.

I got this response

There is no workaround DTS can provide for Feedback ID #FB13432637; it is still under investigation. Please continue to track the problem via the bug report.

2 Likes