Garbage collection e perdite di memoria sulle tabelle hash

3

Stavo leggendo R. Leggi Come essere un programmatore , e sono arrivato attraverso qualcosa che non ho capire:

...even with garbage collection, you can fill up all memory with garbage. A classic mistake is to use a hash table as a cache and forget to remove the references in the hash table. Since the reference remains, the referent is noncollectable but useless. This is called a memory leak. You should look for and fix memory leaks early. If you have long running systems memory may never be exhausted in testing but will be exhausted by the user.

Quindi diciamo che ho una struttura del dizionario in python, indicizzata su hash md5 (è questo il tipo di hashtable a cui si riferisce?). Ad esempio:

x = {}
x['c4ca4238a0b923820dcc509a6f75849b'] = 1
x['c81e728d9d4c2f636f067f89cc14862c'] = 2

Qualcuno può ora guidarmi attraverso il suo esempio? Cosa devo fare concretamente per provocare una perdita di memoria?

    
posta Escher 26.01.2015 - 22:05
fonte

2 risposte

5

È piuttosto semplice. Passa attraverso il codice:

x = {}

Memoria riservata per overhead

x['c4ca4238a0b923820dcc509a6f75849b'] = 1

Memoria per una coppia chiave / valore allocata

x['c81e728d9d4c2f636f067f89cc14862c'] = 2

Memoria per due coppie chiave / valore allocate

Ora immaginiamo di farlo 10.000 volte. Avremo assegnato spazio per 10.000 coppie chiave / valore. Ogni hash aggiunto aumenta l'utilizzo della memoria. Se questi valori non saranno mai più utilizzati, sono "inutili", ma dal momento che hai detto a python di salvarli, non verranno raccolti. Dovresti rimuovere il riferimento in questo modo:

del x['c4ca4238a0b923820dcc509a6f75849b']

Devi farlo perché, in generale, il garbage collector non può sapere che non guarderai mai questo valore.

Il caso di cui si parla qui sta usando un dizionario come cache, e presumibilmente non sai davvero se avrai bisogno di un valore particolare. Inoltre, presumibilmente, non esiste un limite reale al numero di valori. Quindi con un'app di lunga durata, potresti esaurire la memoria. Dovresti avere qualche schema per eliminare elementi dalla cache quando non sono più necessari, presumibilmente testando la recency di utilizzo.

GC non può aiutarti qui più di quanto possa aiutarti se tenti di allocare un array da 100 gigabyte. Lo hai detto esplicitamente attraverso il riferimento non per scartare nessuno dei dati.

    
risposta data 26.01.2015 - 22:52
fonte
0

Per me la semantica non ha importanza. Un programma perde se inizia a utilizzare un carico di memoria della barca e rallenta e rallenta più a lungo lo esegui, come un videogioco che richiede il riavvio ogni 30 minuti perché i frame rate continuano a scendere più a lungo si suona mentre va avanti da prendere megabyte a gigabyte di memoria. La maggior parte dei giochi che presentano questi sintomi che perdono usano la raccolta dei rifiuti e per una buona ragione: la raccolta dei rifiuti tende a scambiarsi immediatamente arresti riproducibili per perdite che volano sotto il radar.

In questo senso, in realtà può essere più facile introdurre perdite in un programma creato usando una lingua con GC, poiché tutto ciò che devi fare per eseguire il root di una risorsa e impedire che venga liberato è memorizzare un riferimento ad esso. / p>

Come esempio, supponiamo che tu abbia un videogioco che ha un sistema fisico per spostare particelle e un renderer per rendere un elenco di particelle a cui fa riferimento. Quando una particella muore, sfuma lo schermo e smette di essere visibile, a quel punto il sistema fisico lo rimuove dall'elenco delle particelle da elaborare.

Voilà, ora hai una perdita perché non hai rimosso il riferimento di particelle dal renderer. Tuttavia, non sarà ovvio in gioco perché le particelle morte hanno un'opacità pari a zero. Ciononostante, il gioco creerebbe una lista sempre più grande di particelle che non verranno mai liberate fino alla chiusura del gioco, e trascorreranno sempre più tempo in loop sempre più grandi nel renderer man mano che l'elenco delle particelle diventerà sempre più grande. Questo potrebbe volare sotto il radar degli sviluppatori indefinitamente al punto in cui in realtà suggeriscono agli utenti di riavviare il gioco di volta in volta se rallenta mentre si abbattono i requisiti di sistema con macchine potenti anche per un semplice gioco 2D.

Nel frattempo in C, questo avrebbe semplicemente portato ad un arresto immediatamente rilevabile dal momento che il sistema fisico avrebbe distrutto manualmente la particella quando sarebbe morta. Il renderer tenterebbe quindi di accedere a una particella che è stata distrutta e molto probabilmente si abbatterà durante il primo test di gioco che è probabilmente più preferibile in questo caso piuttosto che avere perdite di gioco che si interrompono indefinitamente.

Un modo molto risoluto per evitare questo problema è fare affidamento su concetti come i riferimenti deboli e fantasma e decidere chi effettivamente gestisce le particelle. Se si tratta del sistema fisico, il renderer dovrebbe mantenere riferimenti deboli alle particelle in modo che non prolunghino la durata della vita e possano rilevare quando vengono distrutti e si spera che finiscano in un errore di interruzione del gioco se il renderer tenta di accedere a una particella che non esiste più.

In generale, GC non ti protegge dal dover pensare alla gestione delle risorse e chi possiede una risorsa e dover liberare manualmente le risorse (assegnandole come none/nil/null ) per evitare perdite logiche. La sua utilità primaria a mio parere è nel contesto di aree come il multithreading in cui si desidera garantire che un oggetto non venga distrutto fino a quando un thread non ha finito di elaborare l'oggetto.

La soluzione ideale per me se un linguaggio potrebbe mai fornirlo a livello di lingua nativa (il C ++ è il più vicino che possa pensare con shared_ptr , ma è un concetto di libreria e non può rilevare cicli poiché usa il conteggio di riferimento di base ) è uno che potrebbe consentire di optare per la garbage collection per oggetto. Ad esempio, forse l'oggetto viene normalmente distrutto quando esce dall'ambito o viene assegnato un valore null e tutto ciò che lo fa riferimento al di fuori del suo ambito immediato agisce come un puntatore, evitando che l'oggetto venga distrutto. Tuttavia, qualcosa che vuole condividere la proprietà di quell'oggetto, come un thread, potrebbe chiamare un metodo ref per aumentare il suo conteggio di riferimento e deref per decrementarlo per evitare che venga distrutto fino a quando il thread non lo utilizza, oppure potrebbe utilizzare una sorta di concetto di riferimento condiviso che evita la necessità di un esplicito deref nell'ambito del thread, con un costo "paga come lo si utilizza".

    
risposta data 08.12.2017 - 13:55
fonte

Leggi altre domande sui tag