Die Leistung von NSManagedObjectContext wird erheblich reduziert

8

Ich habe Probleme mit einer CoreData-basierten iOS-App, wenn versucht wird, die ursprüngliche Datenbank aus Daten zu erstellen, die vom Server gesendet wurden. Im Grunde sendet der Server 1 MB Brocken von Objekten herunter (ungefähr 3.000 pro Chunk), und der iOS-Client deserialisiert sie und schreibt sie auf die Festplatte.

Was ich sehe, ist, dass für die ersten 8 Chunks (von 44) alles gut geht, dann fällt die Performance dramatisch ab und jeder Chunk beginnt länger und länger zu dauern, wie im Bild unten. Die gesamte Zeit wird in [NSManagedObjectContext save] verbraucht, wie Sie in den Profilerstellungsdaten von Instruments sehen können. Es scheint aber auch, dass die App aus irgendeinem Grund nicht mehr mit 100% der CPU arbeitet, wie es auf Festplatten-I / O wartet etwas.

Ein paar wichtige Fakten darüber, wie ich das mache:

  • Jeder Chunk wird in seiner eigenen NSManagedObjectContext mit seiner eigenen NSAutoreleasePool verarbeitet, so dass kein Objektaufbau in einem nicht gespülten Kontext zwischen der Verarbeitung von Chunks stattfindet.

  • In keinem der Kontexte ist NSUndoManager festgelegt.

  • Es gibt kein mergeChangesFromContextDidSaveNotification: (d. h. die Chunk-Kontexte schieben ihre Änderungen nicht in einen "Master" -Kontext)

  • Ich verwende einen SQLite-basierten Datenspeicher auf iOS 4.3.

  • Die Datensätze, die geschrieben werden, haben Indizes für sie.

  • Der gesamte Synchronisierungsjob wird auf einem einzelnen GCD-Hintergrundthread verarbeitet (d. h. dispatch_queue_create() und dispatch_async() ).

Ich habe keine Ahnung, warum die Performance plötzlich so abfällt oder was man dagegen tun kann. Ich habe herumgestöbert und folgendes gelesen, aber mir ist noch nichts herausgesprungen:

Alle Ideen oder Hinweise, um diese App auf 100.000 Datensätze in der Datenbank zu skalieren, wären sehr willkommen.

Bearbeiten - zusätzliche Statistiken

Diese Instrumentengrafik zeigt die gleiche Simulation wie oben (auf iPad2), enthält jedoch die Statistiken zur Festplattenaktivität und Sie können ziemlich deutlich sehen, dass die Zeit, in der nicht mit 100% CPU gearbeitet wird, mit dem Schreiben zu tun scheint Festplatte.

Ich habe auch den gleichen Synchronisierungsversuch auf dem iOS-Simulator ausgeführt. Die Gesamtspeicherauslastung ist für jeden Chunk mehr oder weniger konstant, außer für ein Dictionary, das Objekt-IDs enthält, die im Laufe der Zeit leicht anwachsen (dies sind jedoch keine CoreData-Objekte oder irgendetwas, das sich auf Speichervorgänge auswirken würde, sie sind nur NSNumbers). Dieses Diktat ist eine kleine Menge an Speicher im Vergleich zu den gesamten Heap und so das Problem nicht zu wenig Speicher.

Was an diesem Test interessant ist, ist, dass das CoreData Save-Instrument meldet, dass die aufeinander folgenden Speichervorgänge ungefähr die gleiche Zeit benötigen, was offensichtlich mit den CPU-Profilinformationen aus dem ersten Satz von Ergebnissen kollidiert. Es scheint, als ob CoreData denkt, dass es die gleiche Zeit braucht, um Änderungen an die DB zu übertragen, aber die Datenbank selbst (d. H. SQLite) benötigt plötzlich viel länger, um diese Änderungen tatsächlich auf die Festplatte zu streamen.

    
glenc 16.06.2011, 15:11
quelle

2 Antworten

7

Ich weiß, dass dies ein altes Problem ist, daher ist dies wahrscheinlich nicht mehr relevant für Sie, aber möglicherweise für jemand anderen.

Ich habe Leistungsprobleme bei der Erstellung einer Core Data-Datenbank über iCloud festgestellt und festgestellt, dass Sie bei inversem Verhalten des Datenmodells in Bezug auf die Leistung unglaublich schlecht betroffen sein können. Die Art und Weise, wie die iCloud Transaktionsprotokollierung implementiert wurde, scheint tatsächlich ein unvermeidliches Problem zu sein. Jede Transaktion, die an iCloud gesendet wird (schauen Sie sich diese auf developer.icloud.com an - sie sind nur gezippte Plisten), zeichnet jede Beziehung auf, die von einer Änderung betroffen ist. Anders als wenn Sie ein Ende einer Beziehung in Core Data ändern und es sich um das inverse Ende kümmert, beendet das Kerndatentransaktionsprotokoll die Aufzeichnung der Änderungen an beiden Enden, anstatt es zu verarbeiten.

Wenn Sie also eine 1: n-Beziehung haben und Sie einen weiteren Datensatz erstellen, der am "vielen" Ende hängen bleibt, wird der Datensatz am "1" -Ende ebenfalls aktualisiert, um der Tatsache Rechnung zu tragen, dass ein neuer Zusatz vorhanden ist Aufzeichnung hängt jetzt davon ab. Wenn Sie eine Architektur haben, die bedeutet, dass Sie ein "Typ" -Objekt haben, an dem viele "Daten" -Objekte hängen bleiben, dann wird jedes Mal, wenn Sie ein neues Datenobjekt hinzufügen, eine Transaktion für den Typ geschrieben - aber Hier ist der Kicker, denn die iCloud Core Data-Transaktionen zeichnen den GESAMTEN Status der bearbeiteten Entitäten auf, nicht nur die Änderungen. JEDE bereits registrierte Beziehung wird ebenfalls dem Protokoll hinzugefügt, nicht nur die, die den neuen untergeordneten Datensatz angibt. Dies kann schnell außer Kontrolle geraten, da die Menge der geschriebenen Daten wächst, wenn die Anzahl der Beziehungen zwischen Entitäten wächst - es dauert immer länger, um Stapel zu speichern.

Ich habe eine Frage wie diese hier vor hier in den Apple-Entwicklerforen geantwortet, die für mich nützlich sein könnten scheint nie in der Lage zu sein, dies prägnant zu beschreiben.

Die einfachste Möglichkeit, die Seeding-Leistung zu verbessern, wenn dieses Szenario Auswirkungen auf Sie hat, besteht darin, inverse Beziehungen zu deaktivieren, dies ist jedoch nicht immer möglich.

    
Rob Glassey 17.05.2013 21:15
quelle
1

Weitere Informationen zu Ihrer Implementierung würden helfen. Führen Sie das zum Beispiel im Hauptthread aus oder implementieren Sie Hintergrundthreads? Ich habe dieses Verhalten jedoch schon einmal gesehen. Wenn umfangreiche Stapeloperationen mit Core Data durchgeführt werden, kann dies verlangsamen, wenn der Speicher nicht ordnungsgemäß verwaltet wird. Haben Sie die Speichernutzung überprüft? Haben Sie nach Lecks gesucht? Eine andere Sache, die Sie versuchen sollten, ist sicherzustellen, dass Sie NSAutoreleasePool bei Bedarf korrekt verwenden. Durch regelmäßiges Entleeren des Pools kann dies die Leistung verbessern.

    
jschmidt 16.06.2011 22:53
quelle