Core Data-Speicherverbrauch beim Importieren eines großen Datasets

8

Ich bin jetzt seit zwei Wochen mit einem fiesen Core Data Problem festgefahren. Ich lese viele Blogposts, Artikel und SO Fragen / Antworten, aber ich bin immer noch nicht in der Lage, mein Problem zu lösen.

Ich habe viele Tests durchgeführt und konnte das größere Problem auf ein kleineres reduzieren. Es wird eine große Erklärung sein, also bleib bei mir!

Problem - Datamodell

Ich muss folgendes Datamodell haben:

Objekt A hat eine Eins-zu-viele-Beziehung zu Objekt B, das eine andere Eins-zu-Viele-Beziehung zu Objekt C hat. Aufgrund von Core Data-Empfehlungen muss ich inverse Beziehungen erstellen, so dass jede Instanz von B auf ihre Eltern A und verweist das gleiche für C, das auf sein Elternteil B zeigt.

%Vor%

Problem - MOC-Setup

Um die Reaktionsfähigkeit reibungslos zu halten, habe ich eine dreistufige managedObjectContext-Struktur erstellt.

  1. Parent MOC - Läuft auf seinem eigenen privaten Thread mit NSPrivateQueueConcurrencyType , ist eng mit dem persistentStoreCoordinator
  2. MainQueue MOC - Wird auf dem mainThread mit NSMainQueueConcurrencyType ausgeführt und hat den übergeordneten MOC 1
  3. Für jeden Parsing-Vorgang erstelle ich einen dritten MOC, der auch seine private Queue hat und übergeordnete mainQueue MOC
  4. hat

Mein Hauptdatencontroller wird als Beobachter der NSManagedObjectContextDidSave -Benachrichtigung von MOC 2 hinzugefügt, so dass jedes Mal, wenn MOC 2 eine performBlock: auf MOC1 speichert, ausgelöst wird, die eine Sicherungsoperation ausführt (asynchron wegen performBlock: ).

Problem - Analyse

Um eine große JSON-Datei in meine Kerndatenstruktur zu zerlegen, schrieb ich einen wiederkehrenden Parser. Dieser Parser beginnt mit dem Erstellen eines neuen MOC (3). Es nimmt dann die Daten für Objekt A und analysiert seine Eigenschaften. Dann liest der Parser die JSON-Relationen für B aus und erstellt die entsprechenden Objekte, die mit Daten gefüllt sind. Diese neuen Objekte werden zu A hinzugefügt, indem addBObject: auf A aufgerufen wird. Da der Parser rekurrent ist, bedeutet das Parsen von B das Parsen von C und hier werden auch neue Objekte erzeugt und an B angehängt. Dies alles geschieht in performBlock: auf MOC 3.

  • Parse (erzeugt 'A'-Objekte und beginnt mit der Analyse von B)
    • Parsen von A (erstellt 'B'-Objekte, hängt sie an A an und beginnt mit der Analyse von C)
      • Parsing B (erzeugt 'C'-Objekte, hängt sie an B an)
        • Parsing C (speichert nur Daten in einem C-Objekt)

Nach jedem Parsingvorgang speichere ich MOC 3 und versende auf der mainThread eine Speicheroperation des Haupt-MOC (2). Wegen der NSManagedObjectContextDidSave -Benachrichtigung speichert MOC 1 asynchron.

%Vor%

Um meinen Speicherbedarf freizugeben und weil ich keine Daten analysieren muss, führe ich folgendes durch:

%Vor%

für jedes A, das ich gerade analysiert habe.

Da ich ungefähr 10 A's parsen muss, die alle ungefähr 10 B's haben, die ungefähr 10 C's haben, werden viele managedObject's erzeugt.

Problem - Instrumente

Alles funktioniert gut. Die einzige Sache ist: Wenn ich das Zuweisungswerkzeug einschalte, sehe ich unveröffentlichte A's, B's und C's. Ich erhalte keine nützlichen Informationen von ihren RetainCounts oder was auch immer. Und weil mein aktuelles Problem ein komplexeres Datenmodell betrifft, werden die lebenden Objekte zu einem ernsthaften Speicherproblem. Kann jemand herausfinden, was ich falsch mache? Das Aufrufen von refreshObjects auf den anderen managedObjectContexts mit dem richtigen managedObject funktioniert ebenfalls nicht. Nur ein harter reset scheint zu funktionieren, aber dann verliere ich meine Zeiger auf lebende Objekte, die von der Benutzeroberfläche verwendet werden.

Andere Lösungen habe ich versucht

  • Ich habe versucht, unidirektionale Beziehungen anstelle von bidirektionalen Beziehungen zu erstellen. Dies erzeugt eine Menge anderer Probleme, die Core Data Inkonsistenzen und seltsames Verhalten verursachen (wie das Dangling von Objekten und Core Data das Generieren von 1-n Relationen anstelle von n-n Relationen (weil die inverse Relation nicht bekannt ist).

  • Ich habe versucht, jedes geänderte oder eingefügte Objekt zu aktualisieren, wenn ich eine NSManagedObjectContextDidSave -Benachrichtigung für ein beliebiges Objekt abrufe

Diese beiden "Lösungen" (die übrigens nicht funktionieren) scheinen auch ein bisschen hacky zu sein. Dies sollte nicht der richtige Weg sein. Es sollte einen Weg geben, dies zum Laufen zu bringen, ohne den Speicherbedarf zu erhöhen und die Oberfläche trotzdem flüssig zu halten?

- CodeDemo

Ссылка

- Weitere Untersuchung

Nach dem Auffrischen jedes Objekts, das jemals benutzt wurde (was mühsame Arbeit ist), wird das Objekt im mainContext (nach einem mainSave) auf 48 Bytes verkleinert. Dies zeigt an, dass die Objekte alle fehlerhaft sind, aber dass noch ein Zeiger im Speicher übrig ist. Wenn wir ungefähr 40.000 Objekte haben, die alle fehlerhaft sind, sind immer noch 1.920 MB im Speicher, die niemals freigegeben werden, bis der persistentManagedObjectContext zurückgesetzt wird. Und das wollen wir nicht machen, weil wir jede Referenz zu einem ManagedObject verlieren.

    
Robin van Dijke 29.10.2012, 20:21
quelle

3 Antworten

5

Robin,

Ich habe ein ähnliches Problem, das ich anders gelöst habe als du. In Ihrem Fall haben Sie ein drittes, IMO, redundantes MOC, das übergeordnete MOC. In meinem Fall habe ich die beiden MOCs auf altmodische Art und Weise über den Persistent Store Coordinator über die DidSave-Benachrichtigungen kommunizieren lassen. Die neuen blockorientierten APIs machen dies viel einfacher und robuster. Dadurch kann ich die untergeordneten MOCs zurücksetzen. Während Sie einen Leistungsvorteil von Ihrem dritten MOC erhalten, ist es kein großer Vorteil gegenüber dem SQLite-Zeilencache, den ich ausnutze. Dein Pfad verbraucht mehr Speicher. Schließlich kann ich, indem ich die DidSave-Benachrichtigungen nachverfolge, die Elemente so zuschneiden, wie sie erstellt werden.

Übrigens, Sie leiden wahrscheinlich auch unter einem massiven Anstieg der Größe Ihrer Regionen MALLOC_TINY und MALLOC_SMALL VM. Mein Trailing-Trimming-Algorithmus ermöglicht es den Zuordnern, Speicherplatz früher wiederzuverwenden und somit das Wachstum dieser problematischen Regionen zu verzögern. Diese Regionen sind meiner Erfahrung nach aufgrund ihres großen Speicherabdrucks ein Hauptgrund dafür, dass meine App, Retweever, getötet wurde. Ich vermute, dass deine App das gleiche Schicksal erleidet.

Wenn die Speicherwarnungen kommen, rufe ich das folgende Snippet auf:

%Vor%

-[NSArray(DDGArray) trimObjects] durchläuft gerade ein Array und aktualisiert das Objekt, wodurch es abgeschnitten wird.

Zusammenfassend scheint Core Data eine Kopie des Schreibalgorithmus für Elemente zu implementieren, die in vielen MOCs vorkommen. Daher haben Sie Dinge auf unerwartete Weise erhalten. Ich konzentriere mich darauf, diese Verbindungen nach dem Import zu unterbrechen, um meinen Speicherbedarf zu minimieren. Mein System scheint aufgrund des SQLite-Zeilen-Caches eine akzeptable Leistung zu erbringen.

Andrew

    
adonoho 31.10.2012, 13:10
quelle
1

Für jedes NSManagedObjectContext , das Sie für einen bestimmten Zweck behalten, sammeln Sie Instanzen von NSManagedObject

A NSManagedObjectContext ist nur ein Stück Notizzettel, das Sie nach Belieben instanziieren und speichern können, wenn Sie Änderungen in NSPersistentStore beibehalten und anschließend verwerfen möchten.

Versuchen Sie für die Parsing-Operationen (Schicht 3) ein MOC für das Op zu erstellen, analysieren Sie es, speichern Sie das MOC und verwerfen Sie es danach.

Es fühlt sich an, als ob mindestens eine Schicht MOC in starken Referenzen zu viele enthalten ist.

Stellen Sie grundsätzlich die Frage nach jedem der MOCs. "Warum halte ich dieses Objekt und die damit verbundenen Kinder am Leben".

    
Warren Burton 30.10.2012 08:41
quelle
0

Ich habe einen Import-Helfer, der etwas sehr ähnliches macht.

Sehen Sie sich den folgenden Code an und sehen Sie, ob er Ihnen hilft

%Vor%     
Rog 30.10.2012 10:31
quelle

Tags und Links