it-swarm.it

Perché Garbage Collection se ci sono puntatori intelligenti

In questi giorni, molte 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ì?

69
Gulshan

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

Suppongo 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 viene raccolto in precedenza e talvolta i GC di tracciamento vengono raccolti in precedenza.

  • Throughput. 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 garbage collection sono molto più incrementali e possono persino essere in tempo reale, ad es. Tapis roulant Baker.

71
Jon Harrop

Dal momento che nessuno lo ha guardato da questo punto di vista, riformulerò la tua domanda: perché mettere qualcosa nella lingua se puoi farlo in una libreria? Ignorare specifici dettagli di implementazione e sintattici, GC/smart i puntatori sono 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. Assicurati che tutto il codice possa usarlo per interagire. Questo è, credo, il il motivo principale per cui il riutilizzo e il codice del codice la condivisione non è decollata fino a quando Java/C #/Python/Ruby. Le biblioteche devono comunicare, e l'unica lingua condivisa affidabile che hanno è quella 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. Puoi 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, const, operatori di dereferenziazione, operatori di riferimento indiretto, indirizzo di, ecc. Se si sposta la gestione 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 sull'investimento. Il valore generato da ogni dato 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, tutti gli utenti la useranno. 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.

66
munificent

Garbage collection sostanzialmente 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.

Puntatori intelligenti si riferiscono solo a qualsiasi struttura che si comporti come un normale puntatore ma abbia alcune funzionalità extra associate. Questi include ma non si limitano alla deallocazione, ma anche a 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 new il tuo 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 assegniamo un puntatore int* p = new int; E non rientra nell'ambito, p stesso viene rimosso dallo stack, ma nessuno si occupa della memoria allocata.

Ora l'unica cosa che hai dall'inizio sono 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à di distruttore per ripulire le risorse ad esso collegate (RAII). Si chiama questo 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 un .Dispose() su alcuni 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à dei puntatori intelligenti, che sono impostati sopra di esso, .NET GC può ad es. 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 di accesso agli oggetti.

36
Dario

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.

4
Tom Anderson

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. L'uso del modello di calcolo dell'attore consente la costruzione di programmi con una quantità minima di stato condiviso tra i thread e la proprietà unica svolge un ruolo importante nel fare in modo che i sistemi ad alte prestazioni facciano un uso efficiente di molti core senza il sovraccarico del condiviso tra- sottopone a thread i dati e i requisiti di mutex impliciti.

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 così tanta "spazzatura" da raccogliere.

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 più orientato alla condivisione più ampia della proprietà e all'accesso simultaneo alle strutture di dati.

4
user1703394

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_ptr in 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 fanno riferimento quei riferimenti, e gli oggetti loro si riferiscono, ecc. Tutto il resto è spazzatura .

In questo modo, i riferimenti circolari non contano più - fintanto che né A, B e C sono raggiungibili, 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.

Naturalmente, 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
Dean Harding

Si tratta di rendimento . 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 della 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 ostacoli . Da notare, non ho esperienza con gli ostacoli, non li ho mai usati.

2
ern0

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. I GC più moderni sono molto bravi 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). Ricevi 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 una struttura di dati mostruosa) diventare visibile per il 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, in modo da poter 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.

2
Michael Kohne

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
sharptooth

È uno spettro.

Se non hai limiti stretti per le prestazioni e sei pronto a mettere il Grind in, finirai ad Assembly o c, 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, conteggio dei riferimenti, 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 fa.

1
drjpizzle

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.

0
user1249