L'utilizzo di una tabella hash nella garbage collection risolverebbe il problema di fermare il mondo di mark e sweep?


13

Nell'algoritmo mark-sweep-compact garbage collection devi spostare il mondo quando si riposizionano oggetti perché il grafico di riferimento diventa incoerente e devi sostituire i valori di tutti i riferimenti che puntano all'oggetto.

E se avessi una tabella hash con ID oggetto come chiave e puntatore come valore, e i riferimenti puntassero a detto ID invece dell'indirizzo dell'oggetto ... allora la correzione dei riferimenti richiederebbe solo la modifica di un valore e la pausa sarebbe necessaria solo se l'oggetto viene tentato di essere scritto durante la copia ...

C'è un errore nella mia linea di pensiero?

Risposte:


19

L'aggiornamento dei riferimenti non è l'unica cosa che richiede una pausa. Gli algoritmi standard comunemente raggruppati sotto "mark-sweep" presuppongono che l'intero grafico dell'oggetto rimanga inalterato mentre viene contrassegnato. La corretta gestione delle modifiche (nuovi oggetti creati, riferimenti modificati) richiede algoritmi alternativi piuttosto complicati, come l'algoritmo tricolore. Il termine ombrello è "raccolta dei rifiuti simultanea".

Sì, anche l'aggiornamento dei riferimenti dopo la compattazione richiede una pausa. E sì, l'uso dell'indirizzamento indiretto (ad es. Tramite un ID oggetto persistente e una tabella hash a puntatori reali) può ridurre notevolmente la pausa. Potrebbe anche essere possibile rendere questa parte priva di blocco se lo si desidera. Sarebbe comunque complicato fare la differenza come qualsiasi concorrenza a basso livello di memoria condivisa, ma non vi è alcuna ragione fondamentale per cui non funzionerebbe.

Tuttavia , avrebbe gravi svantaggi. Oltre a occupare spazio extra ( almeno due parole extra per tutti gli oggetti), rende ogni dereference molto più costosa. Anche qualcosa di semplice come ottenere un attributo ora implica una ricerca completa della tabella hash. Stimerei che il colpo di prestazione fosse molto peggio che per la traccia incrementale.


Bene, oggi abbiamo molta memoria, quindi potremmo dire una tabella da 50 Mb e l'hash potrebbe essere un modulo semplice, quindi solo un'istruzione ...
mrpyo,

3
@mrpyo recuperare le dimensioni della tabella hash, operazione modulo, dereference dall'offset della tabella hash per ottenere il puntatore dell'oggetto reale, dereference all'oggetto stesso. Più probabilmente un po 'di mescolanza del registro. Finiamo con 4+ istruzioni. Inoltre, questo schema presenta problemi riguardanti la localizzazione della memoria: ora, sia la tabella hash che i dati stessi devono adattarsi alla cache.
am

@mrpyo È necessaria una voce (ID oggetto -> indirizzo corrente) per oggetto, giusto? E indipendentemente da quanto a buon mercato la funzione di hash è, si dovrà avere collisioni e necessità di risolverli. Anche quello che ha detto Amon.

@amon è solo questione di tempo prima che le CPU abbiano 50 MB o più di cache :)
Móż

1
@ Ӎσᶎ Al momento possiamo mettere 50 MiB di transistor su un chip e avere ancora una latenza abbastanza bassa da farlo funzionare come cache L1 o L2 (le cache L3 hanno già dimensioni fino a 15 MiB, ma di solito AFAIK off-chip e lontano latenza peggiore di L1 e L2), avremo di conseguenza enormi quantità di memoria principale (e dati da inserire). La tabella non può essere di dimensioni fisse, deve crescere con l'heap.

19

Tutti i problemi nell'informatica possono essere risolti da un altro livello di riferimento indiretto ... ad eccezione del problema di troppi livelli di riferimento indiretto

Il tuo approccio non risolve immediatamente il problema della garbage collection, ma lo sposta solo di un livello. E a quale costo! Ora, ogni accesso alla memoria passa attraverso un'altra dereference puntatore. Non è possibile memorizzare nella cache il percorso del risultato, poiché potrebbe essere stato trasferito nel frattempo, è necessario passare sempre attraverso l'ID oggetto. Nella maggior parte dei sistemi, questa indiretta non è accettabile e si presume che l'arresto del mondo abbia un costo totale di runtime inferiore.

Ho detto che la tua proposta sposta solo il problema, non lo risolve. Il problema riguarda il riutilizzo degli ID oggetto. Gli ID oggetto sono ora il nostro equivalente di puntatori e c'è solo una quantità finita di indirizzi. È ipotizzabile (specialmente su un sistema a 32 bit) che durante la durata del programma siano stati creati più oggetti INT_MAX, ad esempio in un ciclo come

while (true) {
    Object garbage = new Object();
}

Se incrementiamo semplicemente l'ID oggetto per ciascun oggetto, a un certo punto finiremo gli ID. Pertanto, dobbiamo scoprire quali ID sono ancora in uso e quali sono gratuiti in modo che possano essere recuperati. Suona familiare? Ora siamo di nuovo al punto di partenza.


Si presume che si possano usare ID che sono semplicemente "abbastanza grandi", come i bignum a 256 bit? Non sto dicendo che questa idea sia nel complesso buona, ma puoi quasi sicuramente riutilizzare gli IDS.
Valità,

@Vality realisticamente sì - per quanto possiamo vedere ciò aggirerebbe il problema del riutilizzo degli ID. Ma questo è solo un altro argomento "640K dovrebbe essere sufficiente per chiunque", e in realtà non risolve il problema. Un aspetto più catastrofico è che la dimensione di tutti gli oggetti (e la tabella hash) dovrebbe aumentare per accogliere questi pseudo-puntatori sovradimensionati e che durante l'accesso all'hash dobbiamo confrontare questo bigint con altri ID che probabilmente porteranno su più registri e segui più istruzioni per il completamento (su 64 bit: 8 × carico, 4 × confronto, 3 × e che è un aumento di 5 × rispetto agli ints nativi).
amon,

Sì, dopo qualche tempo si esaurirebbero gli ID e sarebbe necessario modificarli tutti, il che richiederebbe una pausa. Ma forse sarebbe un evento raro ...
mrpyo,

@amon Molto d'accordo, tutti i punti molto buoni lì, è molto meglio avere un sistema veramente sostenibile, sono d'accordo. Questo sarà insopportabilmente lento, qualunque cosa tu faccia, è comunque interessante solo in teoria. Personalmente non sono comunque un grande fan dei garbage collector: P
Vality,

@amon: c'è più codice al mondo di questo che andrebbe storto una volta avvolto un ID a 64 bit (584 anni di nanosecondi, e probabilmente puoi organizzare che l'allocazione della memoria prenda 1 ns specialmente se non frammenti il ​​contatore globale che sputa gli ID!). Ma certo, se non hai bisogno di fare affidamento su questo, allora non lo fai.
Steve Jessop,

12

Non ci sono errori nella tua linea di pensiero, hai appena descritto qualcosa di molto vicino a come funzionava il Garbage Collector originale Java

La macchina virtuale Java originale [6] e alcune macchine virtuali Smalltalk usano puntatori indiretti, chiamati handle in [6], per fare riferimento agli oggetti. Le maniglie consentono un facile spostamento degli oggetti durante la raccolta dei rifiuti poiché, con le maniglie, esiste solo un puntatore diretto a ciascun oggetto: quello nella relativa maniglia. Tutti gli altri riferimenti all'oggetto indiretti attraverso l'handle. In tali sistemi di memoria basati su handle, mentre gli indirizzi degli oggetti cambiano nel corso della vita degli oggetti e quindi non possono essere utilizzati per l'hash, gli indirizzi degli handle rimangono costanti.

Hashing efficiente nel tempo e nello spazio di oggetti raccolti nella spazzatura

Nell'attuale implementazione di Sun della Java Virtual Machine, un riferimento a un'istanza di classe è un puntatore a un handle che è esso stesso una coppia di puntatori: uno a una tabella contenente i metodi dell'oggetto e un puntatore all'oggetto Class che rappresenta il tipo di oggetto e l'altro alla memoria allocata dall'heap Java per i dati dell'oggetto.

The Java Virtual Machine Specification (1997)

Quindi funziona, è stato provato e la sua inefficienza ha portato allo sviluppo di marchi generazionali e sistemi di scansione.


Presumibilmente questi handle non erano le chiavi in ​​una tabella hash (come nella domanda), però? Non è necessario, solo una struttura contenente un puntatore. Quindi le maniglie hanno tutte le stesse dimensioni in modo che possano essere allocate da un allocatore di heap. Che per sua natura non ha bisogno di compattazione interna poiché non viene frammentato. Potresti piangere l'incapacità dei blocchi di grandi dimensioni utilizzati da quell'allocatore, di essere essi stessi trasferiti. Che può essere risolto da un altro livello di riferimento indiretto ;-)
Steve Jessop,

@SteveJessop sì, non c'era una hashtable nell'implementazione di gc, sebbene il valore dell'handle fosse anche il valore restituito daObject.getHashCode()
Pete Kirkham,
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.