Usar uma tabela de hash na coleta de lixo resolveria o problema mundial de marcar e varrer?

13

No algoritmo de coleta de lixo mark-sweep-compact, é necessário parar o mundo ao realocar objetos, porque o gráfico de referência se torna inconsistente e é necessário substituir os valores de todas as referências que apontam para o objeto.

Mas e se você tivesse uma tabela de hash com o ID do objeto como chave e ponteiro como valor, e as referências apontassem para o referido ID em vez do endereço do objeto ... a correção de referências exigiria apenas a alteração de um valor e a pausa seria necessária apenas se o objeto é tentado a ser gravado durante a cópia ...

Existe algum erro na minha linha de pensamento?

mrpyo
fonte

Respostas:

19

Atualizar referências não é a única coisa que requer uma pausa. Os algoritmos padrão geralmente agrupados em "varredura de marca" assumem que todo o gráfico de objetos permanece inalterado enquanto está sendo marcado. O manuseio correto de modificações (novos objetos criados, referências alteradas) requer algoritmos alternativos bastante complicados, como o algoritmo tricolor. O termo geral é "coleta de lixo simultânea".

Mas sim, a atualização de referências após a compactação também precisa de uma pausa. E sim, o uso da indireção (por exemplo, através de um ID de objeto persistente e uma tabela de hash para ponteiros reais) pode reduzir bastante a pausa. Pode até ser possível tornar essa peça livre de bloqueios, se assim o desejar. Ainda seria tão complicado acertar quanto qualquer concorrência de memória compartilhada de baixo nível, mas não há razão fundamental para que isso não funcione.

No entanto , isso teria sérias desvantagens. Além de ocupar espaço extra ( pelo menos duas palavras extras para todos os objetos), torna cada desreferência muito mais cara. Mesmo algo tão simples como obter um atributo agora envolve uma pesquisa completa da tabela de hash. Eu estimaria o desempenho atingido como muito pior do que para rastreamento incremental.


fonte
Bem, temos um monte de memória hoje para que pudéssemos ter digamos 50 mesa Mb e de hash pode ser modulo simples para apenas uma instrução ...
mrpyo
3
@mrpyo buscando o tamanho da tabela de hash, operação do módulo, desreferência do deslocamento da tabela de hash para obter o ponteiro do objeto real, desreferência ao próprio objeto. Além disso, possivelmente alguns registros de embaralhamento. Terminamos com mais de 4 instruções. Além disso, esse esquema apresenta problemas relacionados à localidade da memória: Agora, a tabela de hash e os dados em si precisam caber no cache.
amon
@mrpyo Você precisa de uma entrada (ID do objeto -> endereço atual) por objeto, certo? E, independentemente de quão barata seja a função de hash, você terá colisões e precisará resolvê-las. Também o que Amon disse.
@amon é apenas uma questão de tempo antes de CPUs tem 50MB ou mais de memória cache :)
Moz
1
@ Weσᶎ No momento, podemos colocar 50 MiB de transistores em um chip e ainda ter latência baixa o suficiente para funcionar como cache L1 ou L2 (os caches L3 já têm até 15 MiB de tamanho, mas geralmente AFAIK fora do chip e longe latência pior que L1 e L2), teremos quantidades massivas de memória principal (e dados para colocar nela). A tabela não pode ter tamanho fixo, ela deve crescer com a pilha.
19

Todos os problemas em ciência da computação podem ser resolvidos por outro nível de indireção ... exceto pelo problema de muitas camadas de indireção

Sua abordagem não resolve imediatamente o problema da coleta de lixo, mas apenas a eleva um nível. E a que custo! Agora, todo acesso à memória passa por outra desreferência de ponteiro. Não podemos armazenar em cache o local do resultado, pois, embora possa ter sido realocado, sempre devemos passar pelo ID do objeto. Na maioria dos sistemas, esse indireto não é aceitável e presume-se que parar o mundo tenha um custo total de tempo de execução mais baixo.

Eu disse que sua proposta apenas move o problema, não o resolve. O problema está relacionado à reutilização de IDs de objetos. Os IDs de objetos agora são equivalentes a ponteiros e há apenas uma quantidade finita de endereços. É concebível (especialmente em um sistema de 32 bits) que durante a vida útil do seu programa, mais de objetos INT_MAX tenham sido criados, por exemplo, em um loop como

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

Se apenas incrementarmos o ID do objeto para cada objeto, ficaremos sem IDs em algum momento. Portanto, temos que descobrir quais IDs ainda estão em uso e quais são gratuitos para que possam ser recuperados. Soa familiar? Agora estamos de volta à estaca zero.

amon
fonte
Pode-se presumivelmente usar IDs que são "grandes o suficiente", digamos, bignums de 256 bits? Não estou dizendo que essa ideia seja boa no geral, mas é quase certo que você pode reutilizar o IDS.
Validade 18/04
@Vality realisticamente yes - até onde podemos ver, isso contornaria a questão da reutilização de ID. Mas esse é apenas outro argumento de "640K deve ser suficiente para qualquer um" e, na verdade, não resolve o problema. Um aspecto mais catastrófico é que o tamanho de todos os objetos (e a tabela de hash) precisaria aumentar para acomodar esses pseudo-ponteiros superdimensionados, e que durante o acesso ao hash, precisamos comparar esse bigint com outros IDs que provavelmente consumirão vários registros e siga várias instruções para concluir (em 64 bits: carga de 8 ×, comparação de 4 ×, 3 × e um aumento de 5 × em relação às entradas nativas).
amon
Sim, você ficaria sem IDs após algum tempo e precisaria alterar todos eles, o que exigiria uma pausa. Mas possivelmente seria um evento raro ...
mrpyo
@amon Muito de acordo, todos os pontos muito bons lá, é muito melhor ter um sistema genuinamente sustentável, eu concordo. Isso vai ser insuportavelmente lento, seja o que for que você faça, de qualquer maneira é interessante apenas na teoria. Pessoalmente, eu não sou um fã coletor de lixo grande de qualquer maneira no entanto: P
Vality
@amon: há mais código no mundo do que apenas isso que daria errado quando você quebra uma ID de 64 bits (584 anos de nanossegundos, e você provavelmente pode providenciar a alocação de memória para 1ns, especialmente se você não dividir o contador global que cospe os IDs!). Mas claro, se você não precisa confiar nisso, não precisa.
Steve Jessop
12

Não há nenhum erro na sua linha de pensamento, você acabou de descrever algo muito próximo de como o coletor de lixo Java original funcionava

A Java virtual machine original [6] e algumas máquinas virtuais Smalltalk usam ponteiros indiretos, chamados de identificadores em [6], para se referir a objetos. As alças permitem a fácil realocação de objetos durante a coleta de lixo, pois, com as alças, existe apenas um ponteiro direto para cada objeto: aquele em sua alça. Todas as outras referências ao objeto indiretas através do manuseio. Em tais sistemas de memória baseados em identificadores, enquanto os endereços de objetos mudam durante a vida útil dos objetos e, portanto, não podem ser usados ​​para hash, os endereços de identificadores permanecem constantes.

Hashing com economia de espaço e tempo de objetos coletados pelo lixo

Na implementação atual da Sun da Java Virtual Machine, uma referência a uma instância de classe é um ponteiro para um identificador que é um par de ponteiros: um para uma tabela que contém os métodos do objeto e um ponteiro para o objeto Class que representa o tipo do objeto e o outro na memória alocada do heap Java para os dados do objeto.

A especificação da máquina virtual Java (1997)

Portanto, ele funciona, foi tentado e sua ineficiência levou ao desenvolvimento de sistemas geradores de marca e varredura.

Pete Kirkham
fonte
Presumivelmente, essas alças não eram chaves em uma hashtable (como na pergunta). Não há necessidade, apenas uma estrutura contendo um ponteiro. Em seguida, os identificadores têm o mesmo tamanho para que possam ser alocados fora de um alocador de heap. Que por sua natureza não precisa de compactação interna, pois não é fragmentado. Você pode lamentar a incapacidade dos grandes blocos usados ​​por esse alocador para serem realocados. Que pode ser resolvido por um outro nível de engano ;-)
Steve Jessop
@SteveJessop sim, não havia um hashtable na implementação gc, embora o valor da alça foi também o valor retornado porObject.getHashCode()
Pete Kirkham