2016-02-17 20 views
8

Sono di fronte a un errore di memoria nel mio codice. Il mio parser si potrebbe riassumere così:Memoria libera durante il ciclo

# coding=utf-8 
#! /usr/bin/env python 
import sys 
import json 
from collections import defaultdict 


class MyParserIter(object): 

    def _parse_line(self, line): 
     for couple in line.split(","): 
      key, value = couple.split(':')[0], couple.split(':')[1] 
      self.__hash[key].append(value) 

    def __init__(self, line): 
     # not the real parsing just a example to parse each 
     # line to a dict-like obj 
     self.__hash = defaultdict(list) 
     self._parse_line(line) 

    def __iter__(self): 
     return iter(self.__hash.values()) 

    def to_dict(self): 
     return self.__hash 

    def __getitem__(self, item): 
     return self.__hash[item] 

    def free(self, item): 
     self.__hash[item] = None 

    def free_all(self): 
     for k in self.__hash: 
      self.free(k) 

    def to_json(self): 
     return json.dumps(self.to_dict()) 


def parse_file(file_path): 
    list_result = [] 
    with open(file_path) as fin: 
     for line in fin: 
      parsed_line_obj = MyParserIter(line) 
      list_result.append(parsed_line_obj) 
    return list_result 


def write_to_file(list_obj): 
    with open("out.out", "w") as fout: 
     for obj in list_obj: 
      json_out = obj.to_json() 
      fout.write(json_out + "\n") 
      obj.free_all() 
      obj = None 

if __name__ == '__main__': 
     result_list = parse_file('test.in') 
     print(sys.getsizeof(result_list)) 
     write_to_file(result_list) 
     print(sys.getsizeof(result_list)) 
     # the same result for memory usage result_list 
     print(sys.getsizeof([None] * len(result_list))) 
     # the result is not the same :(

L'obiettivo è quello di analizzare il file (di grandi dimensioni), ciascuna linea trasformato in un oggetto JSON che verrà scritto di nuovo ad un file.

Il mio obiettivo è ridurre l'ingombro perché in alcuni casi questo codice genera un errore di memoria. Dopo ogni fout.write vorrei eliminare (memoria libera) il riferimento obj.

Ho provato a impostare obj su Nessuno di chiamare il metodo obj.free_all() ma nessuno di essi libera la memoria. Ho anche usato simplejson piuttosto che json, che hanno ridotto l'impronta ma in alcuni casi ancora troppo grande.

test.in sta cercando come:

test1:OK,test3:OK,... 
test1:OK,test3:OK,... 
test1:OK,test3:OK,test4:test_again... 
.... 
+0

Hai già provato gc.collect()? Vedi: http://stackoverflow.com/questions/1316767/how-can-i-explicitly-free-memory-in-python – JonnyTieM

+0

quanto è grande il tuo test.in? – YOU

+0

Per il parser reale il file di input è di circa 300Mb. –

risposta

1

Affinché obj per essere priva di grado, tutti i riferimenti ad esso devono essere eliminati Il tuo ciclo non lo ha fatto perché il riferimento in list_obj esisteva ancora. Di seguito vi riparare quello:

def write_to_file(list_obj): 
    with open("out.out", "w") as fout: 
     for ix in range(list_obj): 
      obj = list_obj[ix] 
      list_obj[ix] = None 
      json_out = obj.to_json() 
      fout.write(json_out + "\n") 
      obj.free_all() 

In alternativa, si potrebbe distruttivo pop l'elemento dalla parte anteriore della list_obj, anche se questo potrebbe potenzialmente causare problemi di prestazioni se deve riallocare list_obj troppe volte. Non ho sperimentato questo, quindi non ne sono veramente sicuro. La versione è la seguente:

def write_to_file(list_obj): 
    with open("out.out", "w") as fout: 
     while len(list_obj) > 0: 
      obj = list_obj.pop(0) 
      json_out = obj.to_json() 
      fout.write(json_out + "\n") 
      obj.free_all() 
+0

Grazie. I test con questo codice non liberano memoria in base a sys.getsizeof. –

+0

Hai provato a chiamare 'gc.collect()' dopo aver eliminato i riferimenti? –

+0

Sì, l'ho fatto e il tempo del processo è crollato –

4

Non conservare molti un'istanza della classe a matrice, invece farlo in linea. Esempio.

% cat test.in 
test1:OK,test3:OK 
test1:OK,test3:OK 
test1:OK,test3:OK,test4:test_again 

% cat test.py 
import json 

with open("test.in", "rb") as src: 
    with open("out.out", "wb") as dst: 
     for line in src: 
      pairs, obj = [x.split(":",1) for x in line.rstrip().split(",")], {} 
      for k,v in pairs: 
       if k not in obj: obj[k] = [] 
       obj[k].append(v) 
      dst.write(json.dumps(obj)+"\n") 

% cat out.out 
{"test1": ["OK"], "test3": ["OK"]} 
{"test1": ["OK"], "test3": ["OK"]} 
{"test1": ["OK"], "test3": ["OK"], "test4": ["test_again"]} 

Se è lento, non scrivere sul file riga per riga, ma negozio scaricato stringa JSON in ordine e fare dst.write("\n".join(array))

+0

Grazie, ma implicherà il refactoring della mia classe e il mix della logica (output in file e analisi dell'ingresso). Il parser dovrebbe essere in grado di produrre, sia su file che su console, il motivo per cui ho bisogno di memorizzare il risultato analizzato. essere in grado di iter o ottenere una chiave. Alla fine, per gli insuccessi, questo approccio non è adatto a me. –

+0

@AliSAIDOMAR È facile. Basta scrivere un generatore che restituisca il valore (in pratica metti il ​​codice da questa risposta in un 'def' e sostituisci' dst.write' con 'yield'). Quindi puoi semplicemente ripetere il risultato e scrivere a quello che vuoi. – Bakuriu

+0

Il punto principale qui è quello di non memorizzare l'intero risultato (in qualsiasi forma), ma di leggere/analizzare/scrivere riga per riga. Il modo in cui funziona il tuo programma originale, il consumo di memoria è O (lunghezza_file), mentre con l'approccio di YOU (lettura/analisi/scrittura riga per riga) il consumo di memoria è O (max_line_length). – hvb