Por que a coleta de lixo se há indicadores inteligentes

67

Atualmente, muitos idiomas são coletados como lixo. Está disponível até para C ++ por terceiros. Mas o C ++ possui RAII e ponteiros inteligentes. Então, qual é o sentido de usar a coleta de lixo? Está fazendo algo extra?

E em outros idiomas como C #, se todas as referências forem tratadas como ponteiros inteligentes (mantendo o RAII de lado), por especificação e por implementação, ainda haverá a necessidade de coletores de lixo? Se não, então por que não é assim?

Gulshan
fonte
11
Uma coisa que eu entendi depois de fazer essa pergunta: ponteiros inteligentes precisam do RAII para gerenciar a desalocação automática .
precisa
8
Ponteiros inteligentes significam o uso de RAII para GC;)
Dario
Heh, c # deve ter uma opção para manipular toda a "coleta de lixo" com RAII. Referências circulares podem ser detectadas no desligamento do aplicativo, tudo o que precisamos é ver quais alocações ainda estão na memória após a desalocação da classe Program.cs. Em seguida, as referências circulares podem ser substituídas por algum tipo de referência semanal.
AareP

Respostas:

67

Então, qual é o sentido de usar a coleta de lixo?

Suponho que você queira dizer ponteiros inteligentes contados por referência e observarei que eles são uma forma (rudimentar) de coleta de lixo, então responderei à pergunta "quais são as vantagens de outras formas de coleta de lixo em relação a ponteiros inteligentes contados" em vez de.

  • Precisão . Somente a contagem de referência vaza ciclos, de modo que os ponteiros inteligentes contados por referência vazam memória em geral, a menos que outras técnicas sejam adicionadas para capturar ciclos. Depois que essas técnicas são adicionadas, o benefício da simplicidade da contagem de referência desapareceu. Além disso, observe que os GCs de contagem e rastreamento de referência com base no escopo coletam valores em momentos diferentes; às vezes, a contagem de referência coleta mais cedo e, às vezes, os GCs de rastreamento coletam mais cedo.

  • Rendimento . Ponteiros inteligentes são uma das formas menos eficientes de coleta de lixo, particularmente no contexto de aplicativos multithread quando as contagens de referência são aumentadas atomicamente. Existem técnicas avançadas de contagem de referência projetadas para aliviar isso, mas o rastreamento de GCs ainda é o algoritmo de escolha nos ambientes de produção.

  • Latência . As implementações típicas de ponteiros inteligentes permitem que os destruidores avaliem, resultando em tempos de pausa ilimitados. Outras formas de coleta de lixo são muito mais incrementais e podem até ser em tempo real, por exemplo, a esteira de Baker.

Jon Harrop
fonte
23
Não posso acreditar que esta resposta acabou sendo a melhor resposta. Ele mostra uma total falta de entendimento dos ponteiros inteligentes em C ++ e faz afirmações que estão tão fora de sincronia com a realidade que são simplesmente ridículas. Primeiro, o ponteiro inteligente que em um pedaço de código C ++ bem projetado será o mais dominante é o ponteiro exclusivo, não o ponteiro de ações. pt.cppreference.com/w/cpp/memory/unique_ptr E em segundo lugar, não acredito que você realmente reivindique vantagens de 'desempenho' e vantagens em tempo real da coleta de lixo não determinística sobre ponteiros inteligentes.
precisa saber é o seguinte
4
@ user1703394 Parece que o respondente tinha em mente os ponteiros (com ou sem razão, não sei bem o que os OPs sugerem), que têm menos desempenho do que a coleta de lixo não determinística.
Nathan Cooper
8
Esses são todos os argumentos básicos e válidos apenas se você ignorar completamente a pergunta real ou os padrões de uso reais dos diferentes tipos de ponteiros inteligentes. A pergunta era sobre indicadores inteligentes. Sim shared_ptr é um ponteiro inteligente e sim shared_ptr é o ponteiro inteligente mais caro, mas não, não há argumento real para seu uso difundido em qualquer lugar próximo de tornar qualquer argumento de desempenho relevante. Sério, essa resposta deve ser movida para uma pergunta sobre contagem de referência. É uma má referência, contando a resposta para uma boa pergunta sobre ponteiro inteligente.
user1703394
4
"Ponteiros inteligentes não são um conceito mais amplo", sério? Você não tem idéia do quanto essa instrução mina todos os argumentos possivelmente válidos que você criou até agora. Talvez dê uma olhada na propriedade Rust e mova a semântica: koerbitz.me/posts/… É fácil para as pessoas com experiência histórica em C ++ perder o fato de que o C ++ 11 / C ++ 14 com seu modelo de memória melhorou a inteligência ponteiros e semântica de movimento é um animal totalmente diferente dos seus antecessores. Veja como o Rust faz isso, é mais limpo que o C ++ e fornece uma nova perspectiva.
user1703394
6
@ user1703394: "Pausas não vinculadas devido a destruidores são uma propriedade infeliz do RAII usada para recursos que não são de memória". Não, isso não tem nada a ver com recursos que não são de memória.
11136 Jon Harrop
63

Como ninguém o examinou desse ângulo, reformularemos sua pergunta: por que colocar algo no idioma se você pode fazer isso em uma biblioteca? Ignorando a implementação específica e os detalhes sintáticos, o GC / ponteiros inteligentes é basicamente um caso especial dessa questão. Por que definir um coletor de lixo no próprio idioma, se você pode implementá-lo em uma biblioteca?

Existem algumas respostas para essa pergunta. O mais importante primeiro:

  1. Você garante que todo o código possa usá-lo para interoperar. Acho que esse é o grande motivo pelo qual a reutilização e o compartilhamento de código não decolaram até o Java / C # / Python / Ruby. As bibliotecas precisam se comunicar, e a única linguagem compartilhada confiável que eles têm é o que está na própria especificação de linguagem (e, até certo ponto, em sua biblioteca padrão). Se você já tentou reutilizar bibliotecas em C ++, provavelmente experimentou a dor horrenda que nenhuma semântica de memória padrão causa. Eu quero passar uma estrutura para alguma lib. Eu passo uma referência? Ponteiro? scoped_ptr?smart_ptr? Estou passando propriedade, ou não? Existe uma maneira de indicar isso? E se a biblioteca precisar alocar? Eu tenho que dar um alocador? Ao não tornar o gerenciamento de memória parte da linguagem, o C ++ força cada par de bibliotecas a negociar sua própria estratégia específica aqui, e é realmente difícil fazer com que todos concordem. O GC torna isso um problema não completo.

  2. Você pode criar a sintaxe ao seu redor. Como o C ++ não encapsula o gerenciamento de memória, ele precisa fornecer uma série de ganchos sintáticos para permitir que o código no nível do usuário expresse todos os detalhes. Você tem ponteiros, referências, constoperadores de desreferenciamento, operadores de indireção, endereço de etc. Se você colocar o gerenciamento de memória no próprio idioma, a sintaxe poderá ser projetada em torno disso. Todos esses operadores desaparecem e o idioma fica mais limpo e simples.

  3. Você obtém um alto retorno do investimento. O valor que qualquer parte do código gera é multiplicado pelo número de pessoas que o utilizam. Isso significa que, quanto mais usuários você tiver, mais poderá gastar em um software. Quando você move um recurso para o idioma, todos os usuários do idioma o usarão. Isso significa que você pode alocar mais esforço do que uma biblioteca usada apenas por um subconjunto desses usuários. É por isso que linguagens como Java e C # têm VMs absolutamente de primeira qualidade e coletores de lixo de alta qualidade: o custo de desenvolvê-las é amortizado por milhões de usuários.

maravilhoso
fonte
Resposta fantástica! Se ao menos eu pudesse votar mais de uma vez ...
Dean Harding
10
Vale ressaltar que a coleta de lixo não é realmente implementada na linguagem C #, mas no .NET Framework , especificamente no Common Language Runtime (CLR).
Robert Harvey
6
@RobertHarvey: Não é implementado pelo idioma, mas não funcionaria sem a cooperação do idioma. Por exemplo, o compilador deve incluir informações especificando, em cada ponto do código, a localização de cada registro ou deslocamento de quadro de pilha que contém uma referência a um objeto não fixado. Isso é absolutamente sem exceções, seja qual for invariável , que não poderia ser mantido sem o suporte ao idioma.
Supercat 24/12
Uma grande vantagem de ter o GC suportando o idioma e a estrutura necessária é que ele garante que nunca existam referências à memória que possam ser alocadas para algum outro propósito. Se alguém chamar Disposeum objeto que encapsule um bitmap, qualquer referência a esse objeto será uma referência a um objeto bitmap descartado. Se o objeto foi excluído prematuramente enquanto outro código ainda espera usá-lo, a classe de bitmap pode garantir que o outro código falhe de maneira previsível . Por outro lado, o uso de uma referência à memória liberada é Comportamento indefinido.
Supercat 24/12
34

A coleta de lixo basicamente significa que seus objetos alocados são liberados automaticamente em algum momento depois que não são mais acessíveis.

Mais precisamente, eles são liberados quando se tornam inacessíveis para o programa, pois objetos referenciados circularmente nunca seriam liberados.

Ponteiros inteligentes apenas se referem a qualquer estrutura que se comporte como um ponteiro comum, mas com alguma funcionalidade extra anexada. Eles incluem, entre outros, desalocação, mas também cópia em gravação, cheques encadernados, ...

Agora, como você afirmou, ponteiros inteligentes podem ser usados para implementar uma forma de coleta de lixo.

Mas a linha de pensamento segue da seguinte maneira:

  1. A coleta de lixo é uma coisa legal de se ter, pois é conveniente e eu tenho que cuidar de menos coisas
  2. Portanto: eu quero coleta de lixo no meu idioma
  3. Agora, como colocar o GC no meu idioma?

Claro, você pode projetá-lo assim desde o início. O C # foi projetado para ser coletado como lixo, portanto, apenas newseu objeto será liberado quando as referências estiverem fora do escopo. Como isso é feito depende do compilador.

Mas em C ++, não havia coleta de lixo pretendida. Se alocarmos algum ponteiro int* p = new int;e ele ficar fora do escopo, pele próprio será removido da pilha, mas ninguém cuidará da memória alocada.

Agora, a única coisa que você tem desde o início são destruidores determinísticos . Quando um objeto sai do escopo em que foi criado, seu destruidor é chamado. Em combinação com modelos e sobrecarga de operador, você pode projetar um objeto wrapper que se comporte como um ponteiro, mas usa a funcionalidade destruidora para limpar os recursos anexados a ele (RAII). Você chama este de ponteiro inteligente .

Tudo isso é altamente específico em C ++: Sobrecarga de operador, modelos, destruidores, ... Nesta situação específica de linguagem, você desenvolveu indicadores inteligentes para fornecer o GC desejado.

Mas se você projetar uma linguagem com o GC desde o início, isso é apenas um detalhe da implementação. Você acabou de dizer que o objeto será limpo e o compilador fará isso por você.

Ponteiros inteligentes, como em C ++, provavelmente não seriam possíveis em linguagens como C #, que não têm nenhuma destruição determinística (o C # contorna isso, fornecendo açúcar sintático para chamar .Dispose()certos objetos). Os recursos não referenciados serão finalmente recuperados pelo GC, mas indefinidos quando exatamente isso acontecerá.

E isso, por sua vez, pode permitir que o GC faça seu trabalho com mais eficiência. Sendo incorporado mais profundamente à linguagem do que os ponteiros inteligentes, os quais são definidos por cima, o .NET GC pode, por exemplo, atrasar as operações de memória e executá-las em blocos para torná-las mais baratas ou até mover a memória para aumentar a eficiência com base na frequência com que os objetos são acessados.

Dario
fonte
C # tem uma forma de destruição determinística via IDisposablee using. Mas requer um pouco de esforço do programador, e é por isso que geralmente é usado apenas para recursos muito escassos, como identificadores de conexão com o banco de dados.
JSB #
7
@JSBangs: Exatamente. Assim como o C ++ cria ponteiros inteligentes em torno do RAII para obter o GC, o C # é diferente e cria "descartadores inteligentes" em torno do GC para obter o RAII;) De fato, é uma pena que o RAII seja tão difícil no C #, pois é ótimo para exceção. manuseio seguro de recursos. F #, por exemplo, tenta uma simples IDisposablesintaxe por apenas substituindo convencional let ident = valuepor use ident = value...
Dario
@Dario: "O C # segue para o outro lado e cria 'descartadores inteligentes' em torno do GC para obter o RAII". RAII em C # com usingnada tem a ver com coleta de lixo, apenas chama uma função quando uma variável fica fora do escopo, assim como destruidores em C ++.
quer
11
@ Jon Harrop: Por favor, o que? A declaração que você cita é sobre destruidores simples de C ++, sem nenhuma contagem de referência / ponteiros inteligentes / coleta de lixo envolvidos.
Dario
11
"A coleta de lixo basicamente significa apenas que seus objetos alocados são liberados automaticamente quando não estão mais sendo referenciados. Mais precisamente, eles são liberados quando se tornam inacessíveis para o programa, pois os objetos referenciados circularmente nunca seriam liberados de outra forma." ... Mais preciso seria dizer que eles são liberados automaticamente em algum momento depois , não quando . Observe que quando implica que a recuperação acontece imediatamente, quando na verdade a recuperação geralmente ocorre muito mais tarde.
Todd Lehman
4

Há duas grandes diferenças, na minha opinião, entre coleta de lixo e ponteiros inteligentes, usados ​​no gerenciamento de memória:

  1. Ponteiros inteligentes não podem coletar lixo cíclico; coleta de lixo pode
  2. Ponteiros inteligentes executam todo o trabalho nos momentos de referência, desreferenciamento e desalocação, no encadeamento do aplicativo; coleta de lixo não precisa

O primeiro significa que o GC coletará lixo que os ponteiros inteligentes não coletarão; se você estiver usando ponteiros inteligentes, evite criar esse tipo de lixo ou esteja preparado para lidar com ele manualmente.

O último significa que, não importa quão inteligentes sejam os ponteiros inteligentes, sua operação diminuirá a velocidade dos threads de trabalho no seu programa. A coleta de lixo pode adiar o trabalho e movê-lo para outros threads; isso permite que seja mais eficiente em geral (na verdade, o custo de tempo de execução de um GC moderno é menor que um sistema malloc / livre normal, mesmo sem a sobrecarga extra de indicadores inteligentes) e faz o trabalho que ainda precisa ser feito sem entrar no maneira dos encadeamentos do aplicativo.

Agora, observe que indicadores inteligentes, como construções programáticas, podem ser usados ​​para fazer todo tipo de outras coisas interessantes - veja a resposta de Dario - que estão completamente fora do escopo da coleta de lixo. Se você quiser fazer isso, precisará de indicadores inteligentes.

No entanto, para fins de gerenciamento de memória, não vejo nenhuma perspectiva de ponteiros inteligentes substituindo a coleta de lixo. Eles simplesmente não são tão bons nisso.

Tom Anderson
fonte
6
@ Tom: dê uma olhada na resposta de Dario para obter detalhes sobre indicadores inteligentes. Quanto às vantagens dos indicadores inteligentes - a desalocação determinística pode ser uma enorme vantagem quando usada para controlar recursos (não apenas memória). De fato, isso se mostrou tão importante que a Microsoft introduziu o usingbloco nas versões subseqüentes do C #. Além disso, o comportamento não determinístico dos GCs pode ser proibido em sistemas em tempo real (e é por isso que os GCs não são usados ​​lá). Além disso, não vamos esquecer que os GCs são tão complexos para acertar que na verdade vazam memória e são bastante ineficientes (por exemplo, Boehm ...).
Konrad Rudolph
6
O não-determinismo dos GCs é, penso eu, um pouco complicado - existem sistemas de GC que são adequados para uso em tempo real (como o IBM's Recycler), mesmo que os que você vê nas VMs de desktop e servidor não sejam. Além disso, usar ponteiros inteligentes significa usar malloc / free, e as implementações convencionais do malloc não são determinísticas devido à necessidade de pesquisar na lista livre. Os sistemas de GC em movimento têm mais tempos de alocação determinísticos do que os sistemas malloc / free, embora, é claro, menos tempos de alocação determinísticos.
Tom Anderson
3
Quanto à complexidade: sim, os GCs são complexos, mas não estou ciente de que "a maioria realmente vaza memória e é bastante ineficiente", e estaria interessado em ver alguma evidência em contrário. Boehm não é evidência, porque é uma implementação muito primitiva e foi criada para servir uma linguagem C, em que o GC preciso é fundamentalmente impossível devido à falta de segurança do tipo. É um esforço corajoso, e que funcione é muito impressionante, mas você não pode tomá-lo como um exemplo do GC.
Tom Anderson
8
@ Jon: decididamente não besteira. bugzilla.novell.com/show_bug.cgi?id=621899 ou, mais geralmente: flyingfrogblog.blogspot.com/2009/01/… Isso é bem conhecido e é propriedade de todos os GCs conservadores.
Konrad Rudolph
3
"O custo de tempo de execução de um GC moderno é menor que um sistema malloc / free normal". Arenque vermelho aqui. Isso ocorre apenas porque o malloc tradicional é um algoritmo terrivelmente ineficiente. Alocadores modernos que usam vários buckets para tamanhos de bloco diferentes são muito mais rápidos para alocar, muito menos propensos a fragmentação de heap e ainda oferecem uma rápida alocação.
Mason Wheeler
3

O termo coleta de lixo implica que existe algum lixo a ser coletado. No C ++, os ponteiros inteligentes têm vários sabores, principalmente o unique_ptr. O unique_ptr é basicamente uma única propriedade e construção de escopo. Em um pedaço de código bem projetado, a maioria das coisas alocadas em heap normalmente residiria atrás de ponteiros inteligentes unique_ptr e a propriedade desses recursos será bem definida o tempo todo. Não há praticamente nenhuma sobrecarga no unique_ptr e o unique_ptr elimina a maioria dos problemas manuais de gerenciamento de memória que tradicionalmente levavam as pessoas a idiomas gerenciados. Agora que mais núcleos em execução simultânea estão se tornando cada vez mais comuns, os princípios de design que levam o código a usar propriedade exclusiva e bem definida a qualquer momento se tornam mais importantes para o desempenho.

Mesmo em um programa bem projetado, especialmente em ambientes com vários threads, nem tudo pode ser expresso sem estruturas de dados compartilhadas e, para aquelas estruturas de dados que realmente exigem, os threads precisam se comunicar. O RAII no c ++ funciona muito bem para preocupações com a vida útil em uma única instalação de encadeamento; em uma instalação com vários encadeamentos, o tempo de vida dos objetos pode não ser completamente definido hierarquicamente. Para essas situações, o uso do shared_ptr oferece grande parte da solução. Você cria propriedade compartilhada de um recurso e este em C ++ é o único lugar que vemos lixo, mas em quantidades tão pequenas que um programa c ++ projetado adequado deve ser considerado mais para implementar a coleção 'lixo' com ptrs compartilhados do que a coleta de lixo completa como implementado em outros idiomas. C ++ simplesmente não tem tanto 'lixo'

Como declarado por outros, os ponteiros inteligentes contados por referência são uma forma de coleta de lixo, e um deles tem um grande problema. O exemplo usado principalmente como desvantagem das formas de coleta de lixo contadas como referência é o problema com a criação de estruturas de dados órfãs conectadas com ponteiros inteligentes entre si, que criam clusters de objetos que impedem a coleta um do outro. Enquanto em um programa projetado de acordo com o modelo de ator de computação, as estruturas de dados geralmente não permitem que esses clusters não colecionáveis ​​surjam em C ++, quando você usa a abordagem de dados compartilhados para programação multiencadeada, como é usado predominantemente em grande parte da indústria, esses clusters órfãos podem rapidamente se tornar realidade.

Portanto, para resumir tudo, se por uso compartilhado de ponteiro você quer dizer o amplo uso de unique_ptr combinado com o modelo de ator de abordagem computacional para programação multiencadeada e o uso limitado de shared_ptr, que outras formas de coleta de lixo não compram benefícios adicionais. Se, no entanto, uma abordagem de tudo compartilhado permitir que você compartilhe o shared_ptr em todo o lugar, considere mudar modelos de simultaneidade ou mudar para um idioma gerenciado que seja mais voltado para o compartilhamento mais amplo de propriedade e acesso simultâneo às estruturas de dados.

user1703394
fonte
11
Isso significa Rustque não precisa de coleta de lixo?
precisa
11
@Gulshan Rust é um dos poucos idiomas que suporta indicadores únicos seguros.
CodesInChaos
2

A maioria dos ponteiros inteligentes é implementada usando a contagem de referência. Ou seja, cada ponteiro inteligente que se refere a um objeto aumenta a contagem de referência dos objetos. Quando essa contagem chega a zero, o objeto é liberado.

O problema existe se você tiver referências circulares. Ou seja, A tem uma referência a B, B tem uma referência a C e C tem uma referência a A. Se você estiver usando ponteiros inteligentes, para liberar a memória associada a A, B e C, você precisará manualmente entre lá e "quebra" a referência circular (por exemplo, usando weak_ptrem C ++).

A coleta de lixo (normalmente) funciona de maneira bem diferente. Atualmente, a maioria dos coletores de lixo usa um teste de acessibilidade . Ou seja, ele analisa todas as referências na pilha e aquelas que são acessíveis globalmente e, em seguida, rastreia todos os objetos aos quais essas referências se referem e objetos a que se referem, etc. Todo o resto é lixo.

Dessa forma, as referências circulares não importam mais - desde que A, B e C não sejam alcançáveis , a memória poderá ser recuperada.

Existem outras vantagens na coleta de lixo "real". Por exemplo, a alocação de memória é extremamente barata: basta incrementar o ponteiro para o "final" do bloco de memória. A desalocação também possui um custo amortizado constante. Mas é claro que linguagens como C ++ permitem implementar o gerenciamento de memória da maneira que você desejar, para que você possa criar uma estratégia de alocação ainda mais rápida.

Obviamente, em C ++, a quantidade de memória alocada ao heap geralmente é menor que uma linguagem pesada como referência, como C # / .NET. Mas isso não é realmente uma questão de coleta de lixo x ponteiros inteligentes.

De qualquer forma, o problema não é fácil; um é melhor que o outro. Cada um deles tem vantagens e desvantagens.

Dean Harding
fonte
2

É sobre desempenho . A desalocação de memória requer muita administração. Se a desalocação for executada em segundo plano, o desempenho do processo em primeiro plano aumentará. Infelizmente, a alocação de memória não pode ser preguiçosa (os objetos alocados serão usados ​​no próximo momento sagrado), mas a liberação de objetos pode.

Tente em C ++ (sem qualquer GC) para alocar um monte de objetos, imprima "olá" e exclua-os. Você ficará surpreso quanto tempo leva para liberar objetos.

Além disso, o GNU libc fornece ferramentas mais eficazes para desalocar memória, veja obstáculos . Devo notar, não tenho experiência com obstáculos, nunca os usei.

ern0
fonte
Em princípio, você tem razão, mas deve-se notar que esse é um problema que tem uma solução muito simples: use um alocador de pool ou alocador de objetos pequenos para agrupar desalocações. Mas é certo que isso exige (um pouco) mais esforço do que ter um GC sendo executado em segundo plano.
Konrad Rudolph
Sim, claro, o GC é muito mais confortável. (Especialmente para iniciantes: não há problemas de propriedade, não há nem mesmo operador de exclusão.)
ern0
3
@ Ern0: não. O ponto principal dos indicadores inteligentes (contagem de referência) é que não há problema de propriedade nem operador de exclusão.
Konrad Rudolph
3
@ Jon: o que, honestamente, é na maioria das vezes. Se você desejar compartilhar o estado do objeto entre diferentes threads, terá problemas completamente diferentes. Admito que muitas pessoas programam dessa maneira, mas isso é uma conseqüência das abstrações de threading ruins que existiam até recentemente e não é uma boa maneira de executar multithreading.
Konrad Rudolph
11
A desalocação geralmente não é realizada "em segundo plano", mas pausa todos os threads em primeiro plano. A coleta de lixo no modo de lote geralmente é uma conquista de desempenho, apesar da pausa dos encadeamentos em primeiro plano, porque permite que o espaço não utilizado seja consolidado. Pode-se separar os processos de coleta de lixo e compactação de heap, mas - especialmente em estruturas que usam referências diretas ao invés de identificadores - ambos tendem a ser processos de "parar o mundo", e geralmente é mais prático fazê-los juntos.
Supercat
2

A coleta de lixo pode ser mais eficiente - basicamente "agrupa" a sobrecarga do gerenciamento de memória e faz tudo de uma vez. Em geral, isso resultará em menos CPU geral sendo gasta na desalocação de memória, mas significa que você terá uma grande explosão de atividade de desalocação em algum momento. Se o GC não for projetado corretamente, isso poderá se tornar visível para o usuário como uma "pausa" enquanto o GC tenta desalocar a memória. A maioria dos GCs modernos é muito boa em manter isso invisível para o usuário, exceto nas condições mais adversas.

Ponteiros inteligentes (ou qualquer esquema de contagem de referência) têm a vantagem de acontecer exatamente quando você esperaria olhar o código (o ponteiro inteligente fica fora do escopo, algo é excluído). Você recebe pequenas explosões de desalocação aqui e ali. No geral, você pode usar mais tempo de CPU na desalocação, mas como está espalhado por todas as coisas que acontecem no seu programa, é menos provável (menos a desalocação de alguma estrutura de dados de monstros) se tornar visível para o usuário.

Se você estiver fazendo algo em que a capacidade de resposta é importante, sugiro que a contagem inteligente de indicadores / referências informe exatamente quando as coisas estão acontecendo, para que você possa saber ao codificar o que provavelmente ficará visível para os usuários. Em uma configuração de GC, você tem apenas o controle mais efêmero sobre o coletor de lixo e simplesmente precisa tentar solucionar o problema.

Por outro lado, se o objetivo geral da taxa de transferência geral, um sistema baseado em GC pode ser uma escolha muito melhor, pois minimiza os recursos necessários para o gerenciamento de memória.

Ciclos: não considero significativo o problema dos ciclos. Em um sistema em que você tem indicadores inteligentes, você tende a estruturas de dados que não têm ciclos ou é simplesmente cuidadoso com a maneira como desiste dessas coisas. Se necessário, os objetos detentores que sabem como interromper os ciclos nos objetos de propriedade podem ser usados ​​para garantir automaticamente a destruição adequada. Em alguns domínios da programação, isso pode ser importante, mas para a maioria dos trabalhos do dia a dia é irrelevante.

Michael Kohne
fonte
11
"você terá uma grande explosão de atividades de desalocação em algum momento". A esteira de Baker é um exemplo de um coletor de lixo incrivelmente bonito. memorymanagement.org/glossary/t.html#treadmill
Jon Harrop
1

A limitação número um de indicadores inteligentes é que eles nem sempre ajudam contra referências circulares. Por exemplo, você tem o objeto A armazenando um ponteiro inteligente no objeto B e o objeto B está armazenando um ponteiro inteligente no objeto A. Se eles forem deixados juntos sem redefinir um dos ponteiros, eles nunca serão desalocados.

Isso acontece porque um ponteiro inteligente precisa executar uma ação específica que não será triigerada no cenário acima, porque os dois objetos são inacessíveis ao programa. A coleta de lixo irá lidar - ela identificará adequadamente que os objetos não podem ser acessados ​​pelo programa e serão coletados.

dente afiado
fonte
1

É um espectro .

Se você não tem limites estreitos no desempenho e está preparado para se esforçar, você terminará na montagem ou c, com todo o ônus de tomar as decisões certas e toda a liberdade para fazer isso, mas com ele , toda a liberdade de estragar tudo:

"Vou te dizer o que fazer, faça. Confie em mim".

A coleta de lixo é o outro extremo do espectro. Você tem muito pouco controle, mas está resolvido para você:

"Vou te dizer o que eu quero, você faz acontecer".

Isso tem muitas vantagens, principalmente porque você não precisa ser tão confiável quando se trata de saber exatamente quando um recurso não é mais necessário, mas (apesar de algumas das respostas estarem por aqui) não é bom para o desempenho, e a previsibilidade do desempenho. (Como todas as coisas, se você tiver controle e fazer algo estúpido, poderá obter resultados piores. No entanto, sugerir que, em tempo de compilação, saber quais são as condições para liberar memória, não pode ser usado como uma vitória no desempenho. além de ingênuo).

RAII, escopo, contagem de referências, etc , são todos auxiliares para permitir que você avance mais ao longo desse espectro, mas não é todo o caminho até lá. Todas essas coisas ainda requerem uso ativo. Eles ainda permitem e exigem que você interaja com o gerenciamento de memória de uma maneira que a coleta de lixo não.

drjpizzle
fonte
0

Lembre-se de que, no final, tudo se resume a uma CPU executando instruções. Que eu saiba, todas as CPUs de nível consumidor possuem conjuntos de instruções que exigem que você tenha dados armazenados em um determinado local na memória e que possui ponteiros para esses dados. Isso é tudo o que você tem no nível básico.

Tudo além disso, com coleta de lixo, referências a dados que podem ter sido movidos, compactação de heap, etc. etc., está fazendo o trabalho dentro das restrições dadas pelo paradigma "bloco de memória com ponteiro de endereço" acima. A mesma coisa com os ponteiros inteligentes - você AINDA precisa executar o código no hardware real.


fonte