2011-07-18 26 views
8

Questo metodo itera su un elenco di termini nel database, controlla se i termini sono in un testo passato come argomento e, se lo si è, sostituirlo con un collegamento alla pagina di ricerca con il termine come parametro.Perché questo metodo Python perde memoria?

Il numero di termini è elevato (circa 100000), quindi il processo è piuttosto lento, ma questo è Ok poiché viene eseguito come cron job. Tuttavia, fa sì che la memoria consumtion sceneggiatura a razzo e non riesco a trovare il motivo per cui:

class SearchedTerm(models.Model): 

[...] 

@classmethod 
def add_search_links_to_text(cls, string, count=3, queryset=None): 
    """ 
     Take a list of all researched terms and search them in the 
     text. If they exist, turn them into links to the search 
     page. 

     This process is limited to `count` replacements maximum. 

     WARNING: because the sites got different URLS schemas, we don't 
     provides direct links, but we inject the {% url %} tag 
     so it must be rendered before display. You can use the `eval` 
     tag from `libs` for this. Since they got different namespace as 
     well, we enter a generic 'namespace' and delegate to the 
     template to change it with the proper one as well. 

     If you have a batch process to do, you can pass a query set 
     that will be used instead of getting all searched term at 
     each calls. 
    """ 

    found = 0 

    terms = queryset or cls.on_site.all() 

    # to avoid duplicate searched terms to be replaced twice 
    # keep a list of already linkified content 
    # added words we are going to insert with the link so they won't match 
    # in case of multi passes 
    processed = set((u'video', u'streaming', u'title', 
        u'search', u'namespace', u'href', u'title', 
        u'url')) 

    for term in terms: 

     text = term.text.lower() 

     # no small word and make 
     # quick check to avoid all the rest of the matching 
     if len(text) < 3 or text not in string: 
      continue 

     if found and cls._is_processed(text, processed): 
      continue 

     # match the search word with accent, for any case 
     # ensure this is not part of a word by including 
     # two 'non-letter' character on both ends of the word 
     pattern = re.compile(ur'([^\w]|^)(%s)([^\w]|$)' % text, 
          re.UNICODE|re.IGNORECASE) 

     if re.search(pattern, string): 
      found += 1 

      # create the link string 
      # replace the word in the description 
      # use back references (\1, \2, etc) to preserve the original 
      # formatin 
      # use raw unicode strings (ur"string" notation) to avoid 
      # problems with accents and escaping 

      query = '-'.join(term.text.split()) 
      url = ur'{%% url namespace:static-search "%s" %%}' % query 
      replace_with = ur'\1<a title="\2 video streaming" href="%s">\2</a>\3' % url 

      string = re.sub(pattern, replace_with, string) 

      processed.add(text) 

      if found >= 3: 
       break 

    return string 

Probabilmente si vorrà questo codice così:

class SearchedTerm(models.Model): 

[...] 

@classmethod 
def _is_processed(cls, text, processed): 
    """ 
     Check if the text if part of the already processed string 
     we don't use `in` the set, but `in ` each strings of the set 
     to avoid subtring matching that will destroy the tags. 

     This is mainly an utility function so you probably won't use 
     it directly. 
    """ 
    if text in processed: 
     return True 

    return any(((text in string) for string in processed)) 

ho davvero solo due oggetti con riferimenti che potrebbero essere i sospetti qui: terms e processed. Ma non riesco a vedere alcuna ragione per loro di non essere spazzatura raccolta.

EDIT:

penso che dovrei dire che questo metodo viene chiamato all'interno di un metodo modello di Django stessa. Non so se è rilevante, ma qui è il codice:

class Video(models.Model): 

[...] 

def update_html_description(self, links=3, queryset=None): 
    """ 
     Take a list of all researched terms and search them in the 
     description. If they exist, turn them into links to the search 
     engine. Put the reset into `html_description`. 

     This use `add_search_link_to_text` and has therefor, the same 
     limitations. 

     It DOESN'T call save(). 
    """ 
    queryset = queryset or SearchedTerm.objects.filter(sites__in=self.sites.all()) 
    text = self.description or self.title 
    self.html_description = SearchedTerm.add_search_links_to_text(text, 
                    links, 
                    queryset) 

Posso immaginare che il caching automatico Python regex mangia un po 'di memoria. Ma dovrebbe farlo solo una volta e il consumo di memoria sale ad ogni chiamata di update_html_description.

Il problema non è solo che consuma un sacco di memoria, il problema è che non rilasciarlo: ogni telefonata prendono circa il 3% del montone, eventualmente riempiendolo e schiantarsi lo script con 'non può allocare memoria '.

+2

'' È quasi impossibile liberare memoria in un linguaggio spazzato come Python. A rigor di termini, una perdita di memoria è una memoria che non ha riferimenti variabili ad essa. In C++ se si assegna memoria in una classe, ma non si dichiara un distruttore, si può avere una perdita di memoria. Quello che hai qui è semplicemente un elevato consumo di memoria. ' –

+0

:-) Ok. Poi ho ottenuto un elevato consumo di memoria che diventa sempre più alto dopo ogni chiamata. Ma dal momento che è un metodo. E dal momento che non tengo un riferimento a nulla dopo averlo fatto, perché qualcosa consuma ancora memoria? –

+0

Aggiornato la domanda a riguardo. –

risposta

3

L'intero queryset viene caricato in memoria dopo averlo chiamato, è ciò che mangerà la memoria. Si desidera ottenere blocchi di risultati se il set di risultati è così grande, potrebbe essere più risultati nel database ma significherà molto meno consumo di memoria.

+0

Sto bene con l'intero queryset è in memoria, non è molto: 100000 string max avvolto in un oggetto modello.Dopo ogni chiamata, dovrebbe essere comunque garbage collection.Il problema è che ogni chiamata CUMULATIVAMENTE mangia memoria.Prima chiamata prende il 3% della RAM Chiamata successiva al 6% e così via –

+0

Aggiornato la domanda a riguardo –

1

assicurarsi che non si stia eseguendo in DEBUG.

+2

:-) Questo è un server prod e DEBUG è impostato su False. Ma buona cattura, questo è noto per causare perdita di memoria e io la tua risposta mi ha costretto a controllare che. –

-1

Penso che dovrei dire che questo metodo è chiamato all'interno di un metodo di modello Django stesso.

@classmethod

Perché? Perché questo "livello di classe"

Perché questi metodi ordinari non possono avere regole di ambito ordinarie e, nel corso normale degli eventi, raccogliere i dati inutili?

In altre parole (sotto forma di una risposta)

Sbarazzarsi della @classmethod.

+0

^^ @classmethod è per un metodo di classe, chiamato update_html_description (self, links = 3, queryset = None) che è un metodo di istanza. Non c'è confusione qui. –

+0

@ e-satis. Sono sicuro che non c'è confusione. Non c'è anche * bisogno *. –

+0

Ho aggiunto il nome della classe a cui appartengono per chiarire le cose: SearchTerm ha un metodo di classe che collega qualsiasi testo, mentre le istanze Video lo usano per aggiornare la loro descrizione html. Il motivo add_search_links_to_text è un metodo di classe, naturalmente è che si tratta di un metodo di utilità, che non intende agire sulle istanze di SearchTerm. –

1

Sono stato completamente incapace di trovare la causa del problema, ma per ora sto superando questo isolando il frammento infame chiamando uno script (utilizzando subprocess) che contiene questa chiamata di metodo. La memoria sale ma, naturalmente, torna normale dopo la morte del processo python.

Parla di sporco.

Ma questo è tutto quello che ho per ora.

Problemi correlati