Domanda

Python utilizza il metodo del conteggio dei riferimenti per gestire la durata dell'oggetto.Quindi un oggetto che non avrà più alcuna utilità verrà immediatamente distrutto.

Ma, in Java, il GC (garbage collector) distrugge gli oggetti che non vengono più utilizzati in un momento specifico.

Perché Java sceglie questa strategia e qual è il vantaggio che ne deriva?

È migliore dell'approccio Python?

È stato utile?

Soluzione

Ci sono degli svantaggi nell’usare il conteggio dei riferimenti.Uno dei più menzionati sono i riferimenti circolari:Supponiamo che A faccia riferimento a B, B faccia riferimento a C e C faccia riferimento a B.Se A dovesse eliminare il riferimento a B, sia B che C avranno comunque un conteggio dei riferimenti pari a 1 e non verranno eliminati con il conteggio dei riferimenti tradizionale.CPython (il conteggio dei riferimenti non fa parte di Python stesso, ma parte della sua implementazione C) rileva riferimenti circolari con una routine di raccolta dei rifiuti separata che esegue periodicamente...

Altro inconveniente:Il conteggio dei riferimenti può rallentare l'esecuzione.Ogni volta che un oggetto viene referenziato e dereferenziato, l'interprete/VM deve verificare se il conteggio è sceso a 0 (e quindi deallocare in caso affermativo).Garbage Collection non ha bisogno di farlo.

Inoltre, la Garbage Collection può essere eseguita in un thread separato (anche se può essere un po' complicato).Su macchine con molta RAM e per processi che utilizzano la memoria solo lentamente, potresti non voler eseguire affatto GC!Il conteggio dei riferimenti sarebbe un po' uno svantaggio in termini di prestazioni...

Altri suggerimenti

In realtà il conteggio dei riferimenti e le strategie utilizzate dalla Sun JVM sono tutti diversi tipi di algoritmi di garbage collection.

Esistono due approcci generali per rintracciare oggetti morti:tracciamento e conteggio dei riferimenti.Nel tracciare il GC inizia dalle "radici", cose come i riferimenti allo stack, e traccia tutti gli oggetti raggiungibili (live).Tutto ciò che non può essere raggiunto è considerato morto.Nel conteggio dei riferimenti, ogni volta che un riferimento viene modificato, il conteggio degli oggetti coinvolti viene aggiornato.Qualsiasi oggetto il cui conteggio dei riferimenti viene impostato su zero è considerato morto.

Praticamente con tutte le implementazioni GC ci sono dei compromessi, ma il tracciamento è generalmente utile per un throughput elevato (ad es.veloce) ma ha tempi di pausa più lunghi (intervalli più ampi in cui l'interfaccia utente o il programma potrebbero bloccarsi).Il conteggio dei riferimenti può operare in blocchi più piccoli ma sarà complessivamente più lento.Ciò potrebbe significare meno blocchi ma prestazioni complessive inferiori.

Inoltre, un GC con conteggio dei riferimenti richiede un rilevatore di cicli per ripulire eventuali oggetti in un ciclo che non verranno rilevati solo dal conteggio dei riferimenti.Perl 5 non aveva un rilevatore di cicli nella sua implementazione GC e poteva perdere memoria ciclica.

Sono state condotte ricerche anche per ottenere il meglio da entrambi i mondi (tempi di pausa bassi, produttività elevata):http://cs.anu.edu.au/~Steve.Blackburn/pubs/papers/urc-oopsla-2003.pdf

Darren Thomas dà una buona risposta.Tuttavia, una grande differenza tra gli approcci Java e Python è che con il conteggio dei riferimenti nel caso comune (nessun riferimento circolare) gli oggetti vengono ripuliti immediatamente anziché in una data successiva indeterminata.

Ad esempio, posso scrivere codice sciatto e non portatile in CPython come

def parse_some_attrs(fname):
    return open(fname).read().split("~~~")[2:4]

e il descrittore di file per quel file che ho aperto verrà ripulito immediatamente perché non appena il riferimento al file aperto scompare, il file viene sottoposto a garbage collection e il descrittore di file viene liberato.Naturalmente, se eseguo Jython o IronPython o eventualmente PyPy, il garbage collector non verrà necessariamente eseguito molto più tardi;forse prima finirò i descrittori di file e il mio programma andrà in crash.

Quindi DOVRESTI scrivere un codice simile

def parse_some_attrs(fname):
    with open(fname) as f:
        return f.read().split("~~~")[2:4]

ma a volte alle persone piace fare affidamento sul conteggio dei riferimenti per liberare sempre le proprie risorse perché a volte può rendere il codice un po' più breve.

Direi che il miglior garbage collector è quello con le migliori prestazioni, che attualmente sembra essere il garbage collector generazionale in stile Java che può essere eseguito in un thread separato e ha tutte queste ottimizzazioni folli, ecc.Le differenze nel modo in cui scrivi il codice dovrebbero essere trascurabili e idealmente inesistenti.

Penso che l'articolo "Teoria e pratica Java:Una breve storia della raccolta dei rifiuti" di IBM dovrebbe aiutare a spiegare alcune delle domande che hai.

La raccolta dei dati inutili è più veloce (più efficiente in termini di tempo) rispetto al conteggio dei riferimenti, se si dispone di memoria sufficiente.Ad esempio, un gc in copia attraversa gli oggetti "vivi" e li copia in un nuovo spazio e può recuperare tutti gli oggetti "morti" in un solo passaggio contrassegnando un'intera regione di memoria.Questo è molto efficiente, Se hai abbastanza memoria.Le collezioni generazionali utilizzano la consapevolezza che "la maggior parte degli oggetti muore giovane";spesso è necessario copiare solo una piccola percentuale degli oggetti.

[Questo è anche il motivo per cui gc può essere più veloce di malloc/free]

Il conteggio dei riferimenti è molto più efficiente in termini di spazio rispetto alla garbage collection, poiché recupera memoria nel momento stesso in cui diventa irraggiungibile.Questo è utile quando vuoi allegare finalizzatori agli oggetti (ad es.per chiudere un file quando l'oggetto File diventa irraggiungibile).Un sistema di conteggio dei riferimenti può funzionare anche quando solo una piccola percentuale della memoria è libera.Ma il costo di gestione derivante dall'incremento e decremento dei contatori per ogni assegnazione di puntatori richiede molto tempo ed è ancora necessaria una sorta di raccolta dei rifiuti per recuperare i cicli.

Quindi il compromesso è chiaro:se devi lavorare in un ambiente con vincoli di memoria o se hai bisogno di finalizzatori precisi, usa il conteggio dei riferimenti.Se hai abbastanza memoria e hai bisogno di velocità, usa la garbage collection.

Un grande svantaggio del GC di tracciamento di Java è che di tanto in tanto "fermerà il mondo" e bloccherà l'applicazione per un tempo relativamente lungo per eseguire un GC completo.Se l'heap è grande e l'albero degli oggetti è complesso, si bloccherà per alcuni secondi.Inoltre ogni GC completo visita più e più volte l'intero albero degli oggetti, cosa che probabilmente è abbastanza inefficiente.Un altro svantaggio del modo in cui Java esegue GC è che devi dire alla jvm quale dimensione dell'heap desideri (se l'impostazione predefinita non è abbastanza buona);la JVM deriva da quel valore diverse soglie che attiveranno il processo GC quando c'è troppa spazzatura accumulata nell'heap.

Presumo che questa sia in realtà la causa principale della sensazione di scatti di Android (basato su Java), anche sui cellulari più costosi, rispetto alla fluidità di iOS (basato su ObjectiveC e utilizzando RC).

Mi piacerebbe vedere un'opzione jvm per abilitare la gestione della memoria RC e magari mantenere GC solo per l'esecuzione come ultima risorsa quando non è più rimasta memoria.

L'ultima Sun Java VM ha in realtà più algoritmi GC che puoi modificare.Le specifiche Java VM hanno intenzionalmente omesso di specificare il comportamento effettivo del GC per consentire algoritmi GC diversi (e multipli) per diverse VM.

Ad esempio, per tutte le persone a cui non piace l'approccio "ferma il mondo" del comportamento predefinito di Sun Java VM GC, ci sono VM come WebSphere Real Time di IBM che consente l'esecuzione dell'applicazione in tempo reale su Java.

Poiché le specifiche Java VM sono disponibili pubblicamente, non c'è (teoricamente) nulla che impedisca a chiunque di implementare una Java VM che utilizzi l'algoritmo GC di CPython.

Il conteggio dei riferimenti è particolarmente difficile da eseguire in modo efficiente in un ambiente multi-thread.Non so come potresti iniziare a farlo senza entrare in transazioni assistite da hardware o istruzioni atomiche simili (attualmente) insolite.

Il conteggio dei riferimenti è facile da implementare.Le JVM hanno investito molti soldi in implementazioni concorrenti, quindi non dovrebbe sorprendere che implementino ottime soluzioni a problemi molto difficili.Tuttavia, sta diventando sempre più facile indirizzare la tua lingua preferita su JVM.

Verso la fine del gioco, ma penso che una logica significativa per RC in Python sia la sua semplicità.Guarda questo e-mail di Alex Martelli, Per esempio.

(Non sono riuscito a trovare un collegamento al di fuori della cache di Google, la data dell'e-mail è del 13 ottobre 2005 nell'elenco Python).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top