Perché Garbage Collection se ci sono puntatori intelligenti


67

In questi giorni, così tante lingue vengono raccolte. È anche disponibile per C ++ da terze parti. Ma C ++ ha RAII e puntatori intelligenti. Allora, qual è il punto di usare la garbage collection? Sta facendo qualcosa in più?

E in altri linguaggi come C #, se tutti i riferimenti sono trattati come puntatori intelligenti (tenendo da parte RAII), per specifica e per implementazione, ci sarà ancora bisogno di garbage collector? Se no, allora perché non è così?


1
Una cosa che ho capito dopo aver posto questa domanda: i puntatori intelligenti hanno bisogno di RAII per gestire la deallocazione automatica .
Gulshan,

8
I puntatori intelligenti significano usare RAII per GC;)
Dario il

Heh, c # dovrebbe avere un'opzione per gestire tutta la "garbage collection" con RAII. I riferimenti circolari possono essere rilevati allo spegnimento dell'applicazione, tutto ciò di cui abbiamo bisogno è vedere quali allocazioni sono ancora in memoria dopo che la classe Program.cs è stata deallocata. Quindi i riferimenti circolari possono essere sostituiti con alcuni tipi di riferimenti settimanali.
AareP,

Risposte:


67

Quindi, qual è lo scopo dell'utilizzo della garbage collection?

Presumo che tu intenda i puntatori intelligenti contati di riferimento e noterò che sono una forma (rudimentale) di garbage collection, quindi risponderò alla domanda "quali sono i vantaggi di altre forme di garbage collection rispetto ai puntatori intelligenti contati di riferimento" anziché.

  • Precisione . Il conteggio dei riferimenti da solo perde i cicli, quindi i puntatori intelligenti contati dai riferimenti perderanno la memoria in generale a meno che non vengano aggiunte altre tecniche ai cicli di cattura. Una volta aggiunte queste tecniche, il vantaggio della semplicità del conteggio dei riferimenti è svanito. Inoltre, si noti che i GC di conteggio e traccia dei riferimenti basati sull'ambito raccolgono valori in momenti diversi, a volte il conteggio dei riferimenti si raccoglie in precedenza e talvolta i GC di tracciamento vengono raccolti in precedenza.

  • Rendimento . I puntatori intelligenti sono una delle forme meno efficienti di garbage collection, in particolare nel contesto di applicazioni multi-thread quando i conteggi dei riferimenti vengono incrementati atomicamente. Esistono tecniche di conteggio dei riferimenti avanzate progettate per alleviare questo problema, ma la tracciabilità dei GC è ancora l'algoritmo di scelta negli ambienti di produzione.

  • Latenza . Le implementazioni tipiche dei puntatori intelligenti consentono ai distruttori di valanghe, con conseguenti tempi di pausa illimitati. Altre forme di raccolta dei rifiuti sono molto più incrementali e possono persino essere in tempo reale, ad esempio il tapis roulant Baker.


23
Non riesco a credere che questa risposta sia diventata la risposta migliore. Mostra una totale mancanza di comprensione dei puntatori intelligenti C ++ e fa affermazioni che sono così tanto fuori sincrono con la realtà che è semplicemente ridicolo. Innanzitutto, il puntatore intelligente che in un pezzo ben progettato di codice C ++ sarà più dominante è il puntatore univoco, non il puntatore di condivisioni. en.cppreference.com/w/cpp/memory/unique_ptr E in secondo luogo, non posso credere che tu stia effettivamente rivendicando vantaggi "prestazionali" e vantaggi in tempo reale della garbage collection non deterministica su puntatori intelligenti.
user1703394,

4
@ user1703394 Sembra che il risponditore avesse in mente dei puntatori (giustamente o erroneamente, non sono del tutto sicuro di ciò che suggeriscono i PO), che sono meno performanti della garbage collection non deterministica.
Nathan Cooper,

8
Questi sono tutti argomenti di paglia e sono validi solo se si ignora completamente la domanda effettiva o si ignorano i modelli di utilizzo effettivi dei diversi tipi di puntatori intelligenti. La domanda riguardava i puntatori intelligenti. Sì shared_ptr è un puntatore intelligente e sì shared_ptr è il più costoso dei puntatori intelligenti, ma no, non esiste alcun argomento reale per il loro uso pervasivo vicino a rendere rilevante qualsiasi argomento relativo alle prestazioni. Scherzi a parte, questa risposta dovrebbe essere spostata in una domanda sul conteggio dei riferimenti. È una pessima referenza che conta la risposta a una buona domanda con un puntatore intelligente.
user1703394,

4
"I puntatori intelligenti non sono un concetto più ampio", sul serio? Non hai idea di quanto questa affermazione mina tutti gli argomenti probabilmente validi che hai fatto finora. Forse dai un'occhiata alla proprietà di Rust e sposta la semantica: koerbitz.me/posts/… È facile per le persone con esperienza storica con C ++ perdere il fatto che C ++ 11 / C ++ 14 con il suo modello di memoria, migliorato intelligente puntatori e mossa semantica è una bestia completamente diversa rispetto ai loro predecessori. Dai un'occhiata a come lo fa Rust, è più pulito del C ++ e offre una nuova prospettiva.
user1703394,

6
@ user1703394: "Le pause illimitate dovute ai distruttori sono una sfortunata proprietà di RAII utilizzato per risorse non di memoria". No, questo non ha nulla a che fare con risorse non di memoria.
Jon Harrop,

63

Dal momento che nessuno l'ha guardato da questa prospettiva, riformulerò la tua domanda: perché mettere qualcosa nella lingua se riesci a farlo in una biblioteca? Ignorando specifici dettagli di implementazione e sintattici, GC / smart pointers è fondamentalmente un caso speciale di quella domanda. Perché definire un garbage collector nella stessa lingua se è possibile implementarlo in una libreria?

Ci sono un paio di risposte a questa domanda. Il primo più importante:

  1. Garantisci che tutto il codice possa utilizzarlo per interagire. Questo è, credo, il motivo principale per cui il riutilizzo del codice e la condivisione del codice non sono decollati fino a quando Java / C # / Python / Ruby. Le biblioteche devono comunicare e l'unica lingua condivisa affidabile che hanno è ciò che è nelle specifiche della lingua stessa (e, in una certa misura, la sua libreria standard). Se hai mai provato a riutilizzare le librerie in C ++, probabilmente hai provato l'orrendo dolore che nessuna semantica di memoria standard provoca. Voglio passare una struttura a qualche lib. Passo un riferimento? Pointer? scoped_ptr?smart_ptr? Sto passando la proprietà o no? C'è un modo per indicarlo? Cosa succede se la lib deve allocare? Devo dargli un allocatore? Non rendendo la gestione della memoria parte del linguaggio, C ++ obbliga ogni coppia di librerie a negoziare qui la propria strategia specifica ed è davvero difficile farle concordare tutte. GC lo rende un non-problema completo.

  2. È possibile progettare la sintassi attorno ad essa. Poiché C ++ non incapsula la gestione della memoria stessa, deve fornire una gamma di hook sintattici per consentire al codice a livello di utente di esprimere tutti i dettagli. Hai puntatori, riferimenti, constoperatori di dereferenziazione, operatori di riferimento indiretto, indirizzo di, ecc. Se esegui il roll management della memoria nella lingua stessa, la sintassi può essere progettata attorno a quella. Tutti questi operatori scompaiono e la lingua diventa più semplice e pulita.

  3. Ottieni un elevato ritorno sugli investimenti. Il valore generato da una determinata parte di codice viene moltiplicato per il numero di persone che lo utilizzano. Ciò significa che più utenti hai, più puoi permetterti di spendere per un software. Quando sposti una funzione nella lingua, la useranno tutti gli utenti della lingua. Ciò significa che puoi dedicare più sforzi di quanto potresti a una libreria utilizzata solo da un sottoinsieme di quegli utenti. Questo è il motivo per cui linguaggi come Java e C # hanno VM assolutamente di prim'ordine e garbage collector di altissima qualità: il costo dello sviluppo è ammortizzato da milioni di utenti.


Risposta fantastica! Se solo potessi votare più di una volta ...
Dean Harding il

10
Vale la pena notare che la garbage collection non è effettivamente implementata nel linguaggio C # stesso, ma in .NET Framework , in particolare Common Language Runtime (CLR).
Robert Harvey,

6
@RobertHarvey: non è implementato dalla lingua, ma non funzionerebbe senza la cooperazione della lingua. Ad esempio, il compilatore deve includere informazioni che specifichino, in ogni punto del codice, la posizione di ogni registro o offset dello stack-frame che contiene un riferimento a un oggetto non accoppiato. Questo è un invariante assoluto, senza eccezioni , che non potrebbe essere sostenuto senza il supporto linguistico.
supercat

Un grande vantaggio di avere GC che supporta il linguaggio e il framework richiesto è che garantisce che non esisterà mai alcun riferimento alla memoria che possa essere allocato per qualche altro scopo. Se si chiama Disposeun oggetto che incapsula una bitmap, qualsiasi riferimento a tale oggetto sarà un riferimento a un oggetto bitmap disposto. Se l'oggetto è stato eliminato prematuramente mentre altri codici prevedono ancora di usarlo, la classe bitmap può garantire che l'altro codice fallirà in modo prevedibile . Al contrario, l'utilizzo di un riferimento alla memoria liberata è Comportamento indefinito.
supercat

34

La garbage collection in pratica significa solo che gli oggetti allocati vengono automaticamente rilasciati ad un certo punto dopo che non sono più raggiungibili.

Più precisamente, vengono rilasciati quando diventano irraggiungibili per il programma, poiché gli oggetti con riferimenti circolari non verrebbero mai rilasciati altrimenti.

I puntatori intelligenti si riferiscono solo a qualsiasi struttura che si comporta come un normale puntatore ma ha alcune funzionalità extra associate. Questi includono, ma non sono limitati a deallocazione, ma anche controlli di copia, scrittura e rilegatura, ...

Ora, come hai affermato, i puntatori intelligenti possono essere utilizzati per implementare una forma di garbage collection.

Ma il treno del pensiero procede nel modo seguente:

  1. La raccolta dei rifiuti è una cosa interessante da avere, in quanto è conveniente e devo occuparmi di meno cose
  2. Pertanto: desidero la garbage collection nella mia lingua
  3. Ora, come posso ottenere GC nella mia lingua?

Certo, puoi progettarlo in questo modo dall'inizio. C # è stato progettato per essere garbage collection, quindi solo il newtuo oggetto e verrà rilasciato quando i riferimenti non rientrano nell'ambito. Il modo in cui ciò viene fatto dipende dal compilatore.

Ma in C ++ non era prevista la raccolta dei rifiuti. Se allociamo un puntatore int* p = new int;e questo non rientra nell'ambito, esso pstesso viene rimosso dallo stack, ma nessuno si occupa della memoria allocata.

Ora l'unica cosa che hai dall'inizio sono i distruttori deterministici . Quando un oggetto lascia l'ambito in cui è stato creato, viene chiamato il suo distruttore. In combinazione con template e sovraccarico dell'operatore, è possibile progettare un oggetto wrapper che si comporta come un puntatore, ma utilizza la funzionalità distruttore per ripulire le risorse ad esso collegate (RAII). Lo chiami un puntatore intelligente .

Tutto ciò è altamente specifico per C ++: sovraccarico dell'operatore, modelli, distruttori, ... In questa particolare situazione linguistica, hai sviluppato degli indicatori intelligenti per fornirti il ​​GC che desideri.

Ma se si progetta un linguaggio con GC dall'inizio, questo è solo un dettaglio di implementazione. Dici solo che l'oggetto verrà ripulito e il compilatore lo farà per te.

Puntatori intelligenti come in C ++ probabilmente non sarebbero nemmeno possibili in linguaggi come C #, che non hanno alcuna distruzione deterministica (C # aggira il problema fornendo zucchero sintattico per chiamare a .Dispose()su determinati oggetti). Le risorse non referenziate verranno infine recuperate dal GC, ma non saranno definite esattamente quando ciò accadrà.

E questo, a sua volta, può consentire al GC di svolgere il proprio lavoro in modo più efficiente. Essendo integrato nel linguaggio più in profondità rispetto ai puntatori intelligenti, che sono impostati su di esso, .NET GC può ad esempio ritardare le operazioni di memoria ed eseguirle in blocchi per renderle più economiche o persino spostare la memoria in giro per aumentare l'efficienza in base alla frequenza con cui gli oggetti sono accessibili.


C # ha una forma di distruzione deterministica tramite IDisposablee using. Ma richiede un po 'di sforzo da parte del programmatore, motivo per cui di solito viene utilizzato solo per risorse molto scarse come gli handle di connessione al database.
JSB ձոգչ

7
@JSBangs: esattamente. Proprio come C ++ crea puntatori intelligenti attorno a RAII per ottenere GC, C # va dall'altra parte e costruisce "dispositivi intelligenti" attorno a GC per ottenere RAII;) In effetti, è un peccato che RAII sia così difficile in C # in quanto è eccezionale per l'eccezione- gestione sicura delle risorse. F #, ad esempio, prova una IDisposablesintassi più semplice semplicemente sostituendo convenzionale let ident = valuecon use ident = value...
Dario,

@Dario: "C # va dall'altra parte e costruisce" dispositivi intelligenti "attorno al GC per ottenere RAII". RAII in C # with usingnon ha assolutamente nulla a che fare con la garbage collection, chiama semplicemente una funzione quando una variabile esce dall'ambito proprio come i distruttori in C ++.
Jon Harrop,

1
@Jon Harrop: per favore cosa? L'affermazione che citi riguarda i semplici distruttori C ++, senza alcun conteggio di riferimento / puntatori intelligenti / garbage collection coinvolti.
Dario,

1
"La garbage collection in pratica significa solo che gli oggetti allocati vengono automaticamente rilasciati quando non vengono più referenziati. Più precisamente, vengono rilasciati quando diventano irraggiungibili per il programma, poiché gli oggetti con riferimenti circolari non verrebbero mai rilasciati altrimenti". ... Più preciso sarebbe dire che vengono rilasciati automaticamente ad un certo punto dopo , non quando . Si noti che quando implica che la bonifica avviene immediatamente, quando in realtà la bonifica spesso avviene molto più tardi.
Todd Lehman,

4

Ci sono due grandi differenze, a mio avviso, tra garbage collection e smart pointer usati per la gestione della memoria:

  1. I puntatori intelligenti non possono raccogliere la spazzatura ciclica; bidone della spazzatura
  2. I puntatori intelligenti fanno tutto il lavoro nei momenti di riferimento, dereferenziazione e deallocazione, sul thread dell'applicazione; la spazzatura non è necessaria

Il primo significa che GC raccoglierà i rifiuti che i puntatori intelligenti non faranno; se stai utilizzando i puntatori intelligenti, devi evitare di creare questo tipo di immondizia o essere pronto a gestirlo manualmente.

Quest'ultimo significa che, indipendentemente da quanto siano intelligenti i puntatori intelligenti, il loro funzionamento rallenterà i thread di lavoro nel tuo programma. La garbage collection può rinviare il lavoro e spostarlo su altri thread; ciò lo rende complessivamente più efficiente (in effetti, il costo di runtime di un moderno GC è inferiore a un normale sistema malloc / free, anche senza l'overhead aggiuntivo di puntatori intelligenti) e fa quello che deve ancora fare senza entrare nel modo dei thread dell'applicazione.

Ora, nota che i puntatori intelligenti, essendo costrutti programmatici, possono essere usati per fare ogni sorta di altre cose interessanti - vedi la risposta di Dario - che sono completamente al di fuori dell'ambito della garbage collection. Se vuoi farlo, avrai bisogno di puntatori intelligenti.

Tuttavia, ai fini della gestione della memoria, non vedo alcuna prospettiva di puntatori intelligenti che sostituiscono la garbage collection. Semplicemente non sono così bravi.


6
@ Tom: dai un'occhiata alla risposta di Dario per i dettagli sui puntatori intelligenti. Per quanto riguarda i vantaggi dei puntatori intelligenti, la deallocazione deterministica può essere un enorme vantaggio se utilizzata per controllare le risorse (non solo la memoria). In effetti, questo si è rivelato così importante che Microsoft ha introdotto il usingblocco nelle versioni successive di C #. Inoltre, il comportamento non deterministico dei GC può essere proibitivo nei sistemi in tempo reale (motivo per cui i GC non vengono utilizzati lì). Inoltre, non dimentichiamo che i GC sono così complessi da funzionare correttamente che la maggior parte in realtà perde memoria e sono piuttosto inefficienti (ad esempio Boehm ...).
Konrad Rudolph,

6
Il non determinismo dei GC è, credo, un po 'un'aringa rossa - ci sono sistemi GC che sono adatti per l'uso in tempo reale (come IBM Recycler), anche se quelli che vedi nelle VM desktop e server non lo sono. Inoltre, usare i puntatori intelligenti significa usare malloc / free e le implementazioni convenzionali di malloc sono non deterministiche a causa della necessità di cercare l'elenco libero. I sistemi GC mobili hanno tempi di allocazione più deterministici rispetto ai sistemi malloc / liberi, sebbene ovviamente tempi di deallocazione meno deterministici.
Tom Anderson,

3
Per quanto riguarda la complessità: sì, i GC sono complessi, ma non sono consapevole che "la maggior parte in realtà perde memoria e sono abbastanza inefficienti", e sarebbero interessati a vedere alcune prove in caso contrario. Boehm non è una prova, perché è un'implementazione molto primitiva, ed è costruita per servire un linguaggio, C, dove GC accurato è fondamentalmente impossibile a causa della mancanza di sicurezza del tipo. È uno sforzo coraggioso e il suo funzionamento è davvero impressionante, ma non puoi prenderlo come un esempio di GC.
Tom Anderson,

8
@Jon: decisamente non cazzate. bugzilla.novell.com/show_bug.cgi?id=621899 o, più in generale: flyingfrogblog.blogspot.com/2009/01/… Questo è ben noto e una proprietà di tutti i GC conservatori.
Konrad Rudolph,

3
"Il costo di runtime di un moderno GC è inferiore a un normale sistema malloc / free." Aringhe rosse qui. Questo solo perché il tradizionale malloc è un algoritmo orribilmente inefficiente. Gli allocatori moderni che utilizzano più bucket per blocchi di dimensioni diverse sono molto più veloci da allocare, molto meno inclini alla frammentazione dell'heap e offrono comunque una deallocazione rapida.
Mason Wheeler,

3

Il termine garbage collection implica che c'è spazzatura da raccogliere. In C ++ i puntatori intelligenti sono disponibili in più versioni, soprattutto il file unique_ptr. Unique_ptr è fondamentalmente un singolo costrutto di proprietà e ambito. In un pezzo di codice ben progettato, la maggior parte delle cose allocate in heap normalmente risiederebbero dietro puntatori intelligenti unique_ptr e la proprietà di tali risorse sarà sempre ben definita. Non esiste quasi alcun sovraccarico in unique_ptr e unique_ptr elimina la maggior parte dei problemi di gestione manuale della memoria che tradizionalmente spingevano le persone a gestire le lingue. Ora che più core in esecuzione contemporaneamente stanno diventando sempre più comuni, i principi di progettazione che guidano il codice a utilizzare la proprietà unica e ben definita in qualsiasi momento diventano più importanti per le prestazioni.

Anche in un programma ben progettato, specialmente in ambienti multi-thread, non tutto può essere espresso senza strutture di dati condivise e, per quelle strutture di dati che richiedono veramente, i thread devono comunicare. RAII in c ++ funziona abbastanza bene per i problemi di durata in una configurazione a thread singolo, in una configurazione a thread multipli la durata degli oggetti potrebbe non essere completamente definita gerarchicamente. Per queste situazioni, l'uso di shared_ptr offre gran parte della soluzione. Si crea la proprietà condivisa di una risorsa e questo in C ++ è l'unico posto in cui si vede la spazzatura, ma a quantità così piccole che un programma c ++ progettato correttamente dovrebbe essere considerato più per implementare la raccolta 'lettiera' con shared-ptr piuttosto che la raccolta completa di rifiuti come implementato in altre lingue. Il C ++ semplicemente non ha tanta 'spazzatura'

Come affermato da altri, i puntatori intelligenti contati di riferimento sono una forma di garbage collection e un for che ha un grosso problema. L'esempio utilizzato principalmente come svantaggio delle forme di garbage collection contate di riferimento è il problema con la creazione di strutture di dati orfane collegate tra loro con puntatori intelligenti che creano cluster di oggetti che impediscono la raccolta reciproca. Mentre in un programma progettato secondo il modello di calcolo attore, le strutture di dati di solito non consentono l'insorgere di tali cluster non raccoglibili in C ++, quando si utilizza l'ampio approccio di dati condivisi alla programmazione multi-thread, come viene utilizzato prevalentemente in gran parte del settore, questi cluster orfani possono rapidamente diventare una realtà.

Quindi, per riassumere, se per utilizzo del puntatore condiviso intendi l'ampio uso di unique_ptr combinato con il modello attore dell'approccio computazionale per la programmazione multi-thread e l'uso limitato di shared_ptr, rispetto ad altre forme di garbage collection non ti compra nulla vantaggi aggiunti. Se tuttavia un approccio tutto condiviso ti farà finire con shared_ptr in tutto il luogo, allora dovresti considerare di cambiare i modelli di concorrenza o passare a un linguaggio gestito che è più orientato verso una più ampia condivisione della proprietà e un accesso simultaneo alle strutture di dati.


1
Significa che Rustnon è necessaria la raccolta dei rifiuti?
Gulshan,

1
@Gulshan Rust è una delle pochissime lingue che supporta puntatori unici sicuri.
Codici A Caos il

2

La maggior parte dei puntatori intelligenti sono implementati utilizzando il conteggio dei riferimenti. Cioè, ogni puntatore intelligente che fa riferimento a un oggetto incrementa il conteggio dei riferimenti degli oggetti. Quando quel conteggio va a zero, l'oggetto viene rilasciato.

Il problema è se hai riferimenti circolari. Cioè, A ha un riferimento a B, B ha un riferimento a C e C ha un riferimento ad A. Se si utilizzano i puntatori intelligenti, quindi per liberare la memoria associata ad A, B & C è necessario manualmente inserire "spezzare" il riferimento circolare (ad es. usando weak_ptrin C ++).

La garbage collection (in genere) funziona in modo abbastanza diverso. La maggior parte dei raccoglitori di rifiuti in questi giorni utilizza un test di raggiungibilità . Ossia, esamina tutti i riferimenti nello stack e quelli accessibili a livello globale e quindi traccia ogni oggetto a cui si riferiscono quei riferimenti, e gli oggetti a cui si riferiscono, ecc. Tutto il resto è spazzatura.

In questo modo, i riferimenti circolari non contano più - finché non sono raggiungibili né A, B e C , la memoria può essere recuperata.

Ci sono altri vantaggi della "vera" raccolta dei rifiuti. Ad esempio, l'allocazione della memoria è estremamente economica: basta incrementare il puntatore alla "fine" del blocco di memoria. Anche la deallocazione ha un costo ammortizzato costante. Ma ovviamente linguaggi come il C ++ ti consentono di implementare la gestione della memoria praticamente come preferisci, in modo da poter elaborare una strategia di allocazione ancora più veloce.

Ovviamente, in C ++ la quantità di memoria allocata in heap è in genere inferiore a un linguaggio pesante di riferimento come C # /. NET. Ma questo non è davvero un problema di garbage collection vs. smart pointers.

In ogni caso, il problema non è incisivo: uno è migliore dell'altro. Ognuno di essi presenta vantaggi e svantaggi.


2

Si tratta di prestazioni . La memoria non allocata richiede molta amministrazione. Se la non allocazione viene eseguita in background, aumentano le prestazioni del processo in primo piano. Sfortunatamente, l'allocazione di memoria non può essere pigra (gli oggetti allocati saranno usati nel momento sacro del prossimo momento), ma il rilascio di oggetti può.

Prova in C ++ (senza GC) per allocare un grosso gruppo di oggetti, stampa "ciao", quindi eliminali. Sarai sorpreso di quanto tempo ci vorrà per liberare oggetti.

Inoltre, GNU libc fornisce strumenti più efficaci per la memoria non allocata , vedi gli ostacoli . Da notare, non ho esperienza con gli ostacoli, non li ho mai usati.


In linea di principio hai un punto, ma va notato che questo è un problema che ha una soluzione molto semplice: utilizzare un allocatore di pool o un allocatore di piccoli oggetti per raggruppare le deallocazioni. Ma questo richiede certamente (leggermente) più sforzo che avere un GC eseguito in background.
Konrad Rudolph,

Sì, certo, GC è molto più comodo. (Soprattutto per i principianti: non ci sono problemi di proprietà, non c'è nemmeno un operatore di eliminazione.)
ern0

3
@ ern0: no. L'intero punto dei puntatori intelligenti (conteggio dei riferimenti) è che non vi è alcun problema di proprietà e nessun operatore di eliminazione.
Konrad Rudolph,

3
@Jon: che, onestamente, è il più delle volte. Se condividi volenti o nolenti lo stato dell'oggetto tra thread diversi, avrai problemi completamente diversi. Devo ammettere che molte persone programmano in quel modo, ma questa è una conseguenza delle cattive astrazioni di threading che sono esistite fino a poco tempo fa e non è un buon modo per fare il multithreading.
Konrad Rudolph,

1
La deallocazione spesso non viene eseguita "in background", ma mette in pausa tutti i thread in primo piano. La garbage collection in modalità batch è generalmente una vittoria delle prestazioni, nonostante la pausa dei thread in primo piano, perché consente di consolidare lo spazio inutilizzato. Si potrebbero separare i processi di garbage collection e compactificazione dell'heap, ma - specialmente nei framework che usano riferimenti diretti piuttosto che handle - entrambi tendono ad essere processi "stop-the-world", ed è spesso più pratico farli insieme.
supercat

2

La garbage collection può essere più efficiente: fondamentalmente "raggruppa" l'overhead della gestione della memoria e lo fa tutto in una volta. In generale, ciò comporterà una riduzione della CPU complessiva per la disallocazione della memoria, ma a un certo punto si avrà una grande esplosione di attività di disallocazione. Se il GC non è progettato correttamente, questo può diventare visibile all'utente come una "pausa" mentre il GC tenta di disallocare la memoria. La maggior parte dei GC moderni è molto brava a mantenerlo invisibile all'utente, tranne nelle condizioni più avverse.

I puntatori intelligenti (o qualsiasi schema di conteggio dei riferimenti) hanno il vantaggio che si verificano esattamente quando ti aspetteresti di guardare il codice (il puntatore intelligente esce dall'ambito, la cosa viene eliminata). Si ottengono piccole esplosioni di disallocazione qua e là. In generale, potresti utilizzare più tempo della CPU per la disallocazione, ma poiché è distribuito su tutte le cose che accadono nel tuo programma, è meno probabile (a parte la disallocazione di alcune strutture di dati mostruose) diventare visibile al tuo utente.

Se stai facendo qualcosa in cui la reattività è importante, ti suggerirei che i puntatori intelligenti / il conteggio dei riferimenti ti facciano sapere esattamente quando stanno accadendo le cose, così puoi sapere mentre codifichi ciò che è probabile che diventi visibile ai tuoi utenti. In un'impostazione GC hai solo il controllo più effimero sul garbage collector e devi semplicemente provare a aggirare la cosa.

D'altra parte, se il throughput complessivo è il tuo obiettivo, un sistema basato su GC potrebbe essere una scelta molto migliore, in quanto riduce al minimo le risorse necessarie per gestire la memoria.

Cicli: non considero significativo il problema dei cicli. In un sistema in cui hai puntatori intelligenti, tendi verso strutture di dati che non hanno cicli o stai semplicemente attento a come lasciar andare queste cose. Se necessario, gli oggetti custode che sanno come interrompere i cicli negli oggetti di proprietà possono essere utilizzati per assicurare automaticamente la corretta distruzione. In alcuni ambiti della programmazione questo può essere importante, ma per la maggior parte del lavoro quotidiano è irrilevante.


1
"ad un certo punto avrai una grande esplosione di attività di disallocazione". Il tapis roulant di Baker è un esempio di un bidone della spazzatura meravigliosamente incrementale. memorymanagement.org/glossary/t.html#treadmill
Jon Harrop il

1

Limitazione numero uno dei puntatori intelligenti è che non sempre aiutano contro i riferimenti circolari. Ad esempio, hai l'oggetto A che memorizza un puntatore intelligente sull'oggetto B e l'oggetto B sta memorizzando un puntatore intelligente sull'oggetto A. Se vengono lasciati insieme senza ripristinare uno dei puntatori, non verranno mai deallocati.

Ciò accade perché un puntatore intelligente deve eseguire un'azione specifica che non verrà superata nello scenario sopra perché entrambi gli oggetti non sono raggiungibili dal programma. Garbage Collection farà fronte: identificherà correttamente che gli oggetti non sono raggiungibili dal programma e verranno raccolti.


1

È uno spettro .

Se non vuoi avere limiti stretti per le prestazioni e sei pronto a mettere il macello, finirai in assemblea ec, con tutto l'onere di prendere le giuste decisioni e tutta la libertà di farlo, ma con esso , tutta la libertà di sbagliare:

"Ti dirò cosa fare, lo fai. Fidati di me".

La raccolta dei rifiuti è l'altra estremità dello spettro. Hai pochissimo controllo, ma è preso cura di te:

"Ti dirò quello che voglio, lo fai accadere".

Questo ha molti vantaggi, soprattutto che non devi essere altrettanto affidabile quando si tratta di sapere esattamente quando una risorsa non è più necessaria, ma (nonostante alcune delle risposte che fluttuano qui intorno) non è buona per le prestazioni, e la prevedibilità delle prestazioni. (Come tutte le cose, se ti viene dato il controllo e fai qualcosa di stupido puoi avere risultati peggiori. Tuttavia suggerire che sapere in fase di compilazione quali sono le condizioni per essere in grado di liberare memoria, non può essere usato come una vittoria delle prestazioni è oltre l'ingenuo).

RAII, scoping, ref count, ecc. Sono tutti aiutanti per farti muovere ulteriormente lungo quello spettro ma non è completamente lì. Tutte queste cose richiedono ancora un uso attivo. Permettono ancora e richiedono di interagire con la gestione della memoria in un modo in cui la garbage collection non lo fa.


0

Ricorda che alla fine tutto si riduce a una CPU che esegue le istruzioni. Per quanto ne so, tutte le CPU di livello consumer dispongono di set di istruzioni che richiedono la memorizzazione dei dati in un determinato posto e la presenza di puntatori a tali dati. Questo è tutto ciò che hai a livello base.

Tutto ciò che c'è di più con la garbage collection, i riferimenti a dati che potrebbero essere stati spostati, la compattazione dell'heap, ecc. Ecc. Sta facendo il lavoro entro le restrizioni date dal paradigma "memory chunk with a pointer pointer" sopra. Stessa cosa con i puntatori intelligenti: devi ANCORA far funzionare il codice sull'hardware reale.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.