2013-08-30 2 views
12

La mia domanda riguarda i dati principali e la memoria non viene rilasciata. Sto facendo un processo di sincronizzazione che importa i dati da un WebService che restituisce un json. Carico, in memoria, i dati da importare, eseguire il ciclo e creare NSManagedObjects. I dati importati devono creare oggetti che abbiano relazioni con altri oggetti, in totale ce ne sono circa 11.000. Ma per isolare il problema, sto solo creando gli elementi del primo e del secondo livello, lasciando fuori la relazione, quelli sono 9043 oggetti.Importazione dati principali - Non sblocca memoria

Ho iniziato a controllare la quantità di memoria utilizzata, perché l'app si arrestava in modo anomalo alla fine del processo (con l'intero set di dati). Il primo controllo della memoria è dopo aver caricato in memoria il json, in modo che la misurazione prenda davvero in considerazione solo la creazione e l'inserimento degli oggetti in Core Data. Quello che io uso per controllare la memoria utilizzata è di questo codice (source)

 
-(void) get_free_memory { 

    struct task_basic_info info; 
    mach_msg_type_number_t size = sizeof(info); 
    kern_return_t kerr = task_info(mach_task_self(), 
            TASK_BASIC_INFO, 
            (task_info_t)&info, 
            &size); 
    if(kerr == KERN_SUCCESS) { 
     NSLog(@"Memory in use (in bytes): %f",(float)(info.resident_size/1024.0)/1024.0); 
    } else { 
     NSLog(@"Error with task_info(): %s", mach_error_string(kerr)); 
    } 
} 

La mia configurazione:

  • 1 dell'archivio permanente Coordinatore
  • 1 Main ManagedObjectContext (MMC) (NSMainQueueConcurrencyType utilizzata per leggere (solo leggendo) i dati nell'app)
  • 1 Sfondo ManagedObjectContext (BMC) (NSPrivateQueueConcurrencyType, undoManager è impostato su nil, utilizzato per importare i dati)

Il BMC è indipendente da MMC, quindi BMC non è un contesto secondario di MMC. E non condividono alcun contesto genitore. Non ho bisogno di BMC per notificare le modifiche a MMC. Quindi BMC ha solo bisogno di creare/aggiornare/cancellare i dati.

Plaform:

  • iPad 2 e 3
  • iOS, ho provato a impostare la destinazione di distribuzione di 5.1 e 6.1. Non c'è alcuna differenza
  • XCode 4.6.2
  • ARC

Problema: importazione dei dati, la memoria utilizzata non smette di aumentare e iOS non sembra essere in grado di drenare la memoria anche dopo la fine del processo. Che, nel caso in cui il campione di dati aumenta, porta a Avvisi di memoria e dopo la chiusura dell'app.

di ricerca:

  1. documentazione di Apple

  2. buon riassunto dei punti di avere in mente durante l'importazione dati a Core Data (Stackoverflow)

  3. Test eseguiti e analisi del rilascio di memoria.Sembra avere lo stesso problema di me, e ha inviato una segnalazione di bug di Apple senza alcuna risposta da parte di Apple. (Source)

  4. Importazione e visualizzazione di grandi insiemi di dati (Source)

  5. Indica il modo migliore per importare grandi quantità di dati. Sebbene menzioni:

    "Sono in grado di importare milioni di record in un 3MB di memoria stabile senza chiamata -reset."

    Questo mi fa pensare che questo potrebbe essere in qualche modo possibile? (Source)

Test:

dati di esempio: la creazione di un totale di 9043 oggetti.

  • spento la creazione di relazioni, la documentazione dice che sono "costoso"
  • Nessun recupero è stato fatto

Codice:


- (void)processItems { 
    [self.context performBlock:^{ 
     for (int i=0; i < [self.downloadedRecords count];) { 
      @autoreleasepool 
      { 
       [self get_free_memory]; // prints current memory used 
       for (NSUInteger j = 0; j < batchSize && i < [self.downloadedRecords count]; j++, i++) 
       { 
        NSDictionary *record = [self.downloadedRecords objectAtIndex:i]; 

        Item *item=[self createItem]; 
        objectsCount++; 

        // fills in the item object with data from the record, no relationship creation is happening 
        [self updateItem:item WithRecord:record]; 

        // creates the subitems, fills them in with data from record, relationship creation is turned off 
        [self processSubitemsWithItem:item AndRecord:record]; 
       } 
       // Context save is done before draining the autoreleasepool, as specified in research 5) 
       [self.context save:nil]; 

       // Faulting all the created items 
       for (NSManagedObject *object in [self.context registeredObjects]) { 
        [self.context refreshObject:object mergeChanges:NO]; 
       } 
       // Double tap the previous action by reseting the context 
       [self.context reset]; 
      } 
     } 
    }]; 
    [self check_memory];// performs a repeated selector to [self get_free_memory] to view the memory after the sync 
} 

Measurment:

Passa da 16,97 MB a 30 MB, dopo la sincronizzazione scende a 28 MB. La ripetizione della chiamata get_memory ogni 5 secondi mantiene la memoria a 28 MB.

Altri test senza alcuna fortuna:

  • ricreare l'archivio permanente, come indicato nella ricerca 2) non ha effetto
  • testato per lasciare che il filo aspettare un po 'per vedere se la memoria restituisce, ad esempio 4
  • impostazione contesto a zero dopo l'intero processo
  • Esecuzione dell'intero processo senza salvare il contesto in qualsiasi punto (perdendo così le informazioni). Ciò ha dato come risultato il mantenimento di una minore quantità di memoria, lasciandola a 20 MB. Ma continua a non diminuire e ... Ho bisogno delle informazioni memorizzate :)

Forse mi manca qualcosa ma ho davvero provato molto, e dopo aver seguito le linee guida mi sarei aspettato di vedere la memoria diminuire ancora. Ho eseguito gli strumenti di allocazione per controllare la crescita dell'heap e anche questo sembra andare bene. Inoltre nessuna perdita di memoria.

Sono a corto di idee per testare/regolare ... Sarei davvero grato se qualcuno potesse aiutarmi con idee su che altro potrei testare, o forse indicando ciò che sto facendo male. O è proprio così, come dovrebbe funzionare ... che dubito ...

Grazie per qualsiasi aiuto.

EDIT

ho usato strumenti al profilo l'utilizzo della memoria con il modello Activity Monitor e il risultato mostrato in "Real Memory Usage" è la stessa di quella che viene stampato nella console con il get_free_memory e la memoria non sembra mai essere rilasciata.

+0

Sarebbe molto, molto meglio analizzare l'utilizzo della memoria in Strumenti anziché con quella funzione. Oltre a mostrare l'uso della memoria in momenti diversi, collegherà le allocazioni di memoria a specifiche linee di codice. –

+0

Inoltre, sembra che ogni passaggio del ciclo interno elaborerà lo stesso record, poiché 'i' non cambia durante quel ciclo. –

+0

Ciao Tom, Grazie per la risposta. Ho profilato l'app con il modello Allocations per la crescita dell'heap e il modello Leak, tutto sembra a posto ... Consiglieresti di usare il monitor Memory? Non ho molta esperienza con questo profiler. In realtà il 'i' sta aumentando di uno nel secondo ciclo (è necessario scorrere per vederlo, il codice non si adatta completamente alla cornice). Facendolo in questo modo, sono in grado di salvare i batch prima che finisca il blocco '@ autoreleasepool', che è un suggerimento che ho letto in Research 5) –

risposta

10

Ok questo è abbastanza imbarazzante ... Zombies sono stati abilitati sul programma, sugli argomenti che sono state spente, ma sul Diagnostica "Enable Zombie oggetti" è stata controllata ...

Girando questo fuori mantiene la memoria stabile .

Grazie per quelli che hanno letto la domanda e hanno provato a risolverlo!

+0

Imbarazzante ... ma mi hai salvato da un altro paio di ore di frustrazione ... Ho completamente dimenticato di averli abilitati e stavo impazzendo cercando di capire perché la mia app stava accumulando memoria! – RyanG

+0

Lo stesso qui grazie per aver condiviso solo 90 minuti sprecati della mia vita. – Gapp

+0

GRAZIE di un altro sviluppatore imbarazzato: / – Rog

2

Mi sembra che la chiave per la rimozione della fonte preferita ("3MB, milioni di record") sia il batch menzionato oltre alla disattivazione del gestore di annullamento, che è anche raccomandato da Apple e molto importante).

Penso che la cosa importante qui sia che questo batch deve essere applicato anche allo @autoreleasepool.

Non è sufficiente scaricare il pool di autorelease ogni 1000 iterazioni. È necessario salvare effettivamente il MOC, quindi scaricare la piscina.

Nel codice, provare a inserire un secondo @autoreleasepool nel secondo ciclo. Quindi regolare le dimensioni del batch per la messa a punto.

Ho eseguito test con oltre 500.000 record su un iPad originale 1. La dimensione della stringa JSON da sola era vicina a 40 MB. Tuttavia, tutto funziona senza arresti anomali e alcune regolazioni portano persino a una velocità accettabile. Nei miei test, ho potuto richiedere fino all'app. 70 MB di memoria su un iPad originale.

+0

Ciao Mundi, grazie per la tua risposta. In realtà è un sollievo che tu sia stato in grado di fare test con così tanti dischi. Spero di poter raggiungere anche questo. Se metto un secondo '@ autoreleasepool' nel secondo ciclo for, non accadrà il salvataggio, in questo caso, dopo aver scaricato il pool? In realtà, ho fatto il setup con i due for loops, allo scopo di salvare dopo aver drenato il pool seguendo la citazione che hai citato ... Questo '@ autoreleasepool' non avrebbe influito sul salvataggio? –

+0

Finché si salva * all'interno * di '@ autoreleasepool' si dovrebbe avere un miglioramento della memoria. Nota che nel tuo codice puoi salvare più volte in uno '@ autoreleasepool'. – Mundi

+0

Sto solo salvando una volta in ogni '@ autoreleasepool' se non sbaglio.Si noti che il ciclo for interno è il batch (incrementa anche l'indice 'i' del ciclo esterno), i salvataggi di contesto, i pool drain e sul ciclo successivo del outer per un nuovo' @ autoreleasepool' viene creato e il successivo batch sta per essere elaborato –