Desvantagens do gerenciamento de memória baseado em escopo

37

Eu realmente gosto de gerenciamento de memória baseado em escopo (SBMM), ou RAII , como é mais comumente (confusamente?) referido pela comunidade C ++. Até onde eu sei, exceto pelo C ++ (e C), não há outra linguagem mainstream em uso atualmente que faça do SBMM / RAII seu principal mecanismo de gerenciamento de memória, e prefere usar o coletor de lixo (GC).

Acho isso bastante confuso, já que

  1. O SBMM torna os programas mais deterministas (você pode dizer exatamente quando um objeto é destruído);
  2. em linguagens que usam o GC, você geralmente precisa fazer o gerenciamento manual de recursos (por exemplo, fechando arquivos em Java), o que parcialmente anula o objetivo do GC e também é propenso a erros;
  3. A memória de heap
  4. também pode (muito elegantemente, imo) ser limitada pelo escopo (consulte std::shared_ptr em C ++).

Por que o SBMM não é mais amplamente usado? Quais são as suas desvantagens?

    
por Paul 09.03.2014 / 14:32
fonte

8 respostas

27

Vamos começar postulando que a memória é de longe (dezenas, centenas ou mesmo milhares de vezes) mais comum do que todos os outros recursos combinados. Cada variável, objeto, membro do objeto precisa de alguma memória alocada para ele e liberada posteriormente. Para cada arquivo que você abre, você cria dezenas de milhões de objetos para armazenar os dados retirados do arquivo. Cada fluxo TCP vai junto com um número ilimitado de cadeias de bytes temporárias criadas para serem gravadas no fluxo. Estamos na mesma página aqui? Ótimo.

Para que o RAII funcione (mesmo que você tenha ponteiros inteligentes prontos para cada caso de uso sob o sol), você precisa ter a propriedade correta. Você precisa analisar quem deve possuir este ou aquele objeto, quem não deve, e quando a propriedade deve ser transferida de A para B. Claro, você pode usar propriedade compartilhada para tudo , mas então você seria emulando um GC através de ponteiros inteligentes. Nesse ponto, torna-se muito mais fácil e mais rápido construir o GC na linguagem.

A coleta de lixo libera você dessa preocupação pelo recurso mais utilizado, a memória. Claro, você ainda precisa tomar a mesma decisão para outros recursos, mas eles são muito menos comuns (veja acima), e a propriedade complicada (por exemplo, compartilhada) também é menos comum. O fardo mental é reduzido significativamente.

Agora, nomeie algumas desvantagens para tornar todos os valores coletados no lixo. No entanto, integrar os tipos de valor GC e com memória segura com RAII em um idioma é extremamente difícil, então talvez seja melhor migrar esses trade offs por outros meios?

A perda de determinismo acaba por não ser tão ruim na prática, porque afeta apenas o tempo de vida do objeto determinístico. Conforme descrito no próximo parágrafo, a maioria dos recursos (além da memória, que é abundante e pode ser reciclada de forma bastante preguiçosa) não está vinculada ao tempo de vida do objeto nesses idiomas. Existem alguns outros casos de uso, mas eles são raros na minha experiência.

Seu segundo ponto, o gerenciamento manual de recursos, é abordado atualmente por meio de uma instrução que realiza limpeza baseada em escopo, mas não acopla essa limpeza à vida útil do objeto (portanto, não interagindo com o GC e a segurança da memória). Isso é using em C #, with em Python, try -with-resources em versões recentes do Java.

    
por 09.03.2014 / 15:13
fonte
14

RAII tamb� segue da gest� autom�ica de mem�ia de contagem de refer�cia, e. como usado por Perl. Embora a contagem de referência seja fácil de implementar, determinística e bastante eficiente, ela não pode lidar com referências circulares (elas causam um vazamento) e é por isso que ela não é comumente usada.

Linguagens coletadas com garbage não podem usar o RAII diretamente, mas geralmente oferecem sintaxe com um efeito equivalente. Em Java, temos a declaração try-with-ressource

try (BufferedReader br = new BufferedReader(new FileReader(path))) { ... }

que chama automaticamente .close() no recurso na saída do bloco. C # tem a interface IDisposable , que permite que .Dispose() seja chamado ao deixar uma instrução using (...) { ... } . Python possui a instrução with :

with open(filename) as f:
    ...

que funciona de maneira semelhante. Em um giro interessante sobre isso, o método de abertura de arquivos do Ruby recebe um retorno de chamada. Depois que o retorno de chamada foi executado, o arquivo é fechado.

File.open(name, mode) do |f|
    ...
end

Acho que o Node.js usa a mesma estratégia.

    
por 09.03.2014 / 15:01
fonte
14

Na minha opinião, a vantagem mais convincente da coleta de lixo é que ela permite composability . A correção do gerenciamento de memória é uma propriedade local no ambiente de coleta de lixo. Você pode examinar cada parte isoladamente e determinar se pode vazar memória. Combine qualquer número de partes corretas da memória e elas se mantêm corretas.

Quando você confia na contagem de referências, perde essa propriedade. Se o seu aplicativo pode vazar memória, ele se tornará uma propriedade global de todo o aplicativo com contagem de referência. Cada nova interação entre as partes tem a possibilidade de usar a propriedade errada e quebrar o gerenciamento de memória.

Tem um efeito muito visível no design de programas nas diferentes linguagens. Programas em linguagens GC tendem a ser um pouco mais de sopas de objetos com muitas interações, enquanto que em linguagens sem GC, tendem a preferir partes estruturadas com interações estritamente controladas e limitadas entre elas.

    
por 09.03.2014 / 16:36
fonte
7

Closures são uma característica essencial de praticamente todas as linguagens modernas. Eles são muito fáceis de implementar com o GC e muito difíceis (embora não impossíveis) de acertar com o RAII, já que uma de suas principais características é que eles permitem que você abstraia durante a vida útil de suas variáveis!

O C ++ só os conseguiu 40 anos depois de todos os outros, e foi preciso muito trabalho de muita gente inteligente para acertar. Em contraste, muitas linguagens de script projetadas e implementadas por pessoas com conhecimento zero no design e implementação de linguagens de programação as possuem.

    
por 09.03.2014 / 15:13
fonte
5
  1. SBMM makes programs more deterministic (you can tell exactly when an object is destroyed);

Para a maioria dos programadores, o sistema operacional é não-determinístico, seu alocador de memória é não-determinístico e a maioria dos programas que eles escrevem são simultâneos e, portanto, inerentemente não-determinísticos. Adicionando a restrição que um destruidor é chamado exatamente no final do escopo, em vez de um pouco antes ou um pouco depois, não é um benefício prático significativo para a grande maioria dos programadores.

  1. in languages that use GC you often have to do manual resource management (see closing files in Java, for example), which partly defeats the purpose of GC and is also error prone;

Veja using em C # e use em F #.

  1. heap memory can also (very elegantly, imo) be scope-bound (see std::shared_ptr in C++).

Em outras palavras, você poderia pegar o heap que é uma solução de propósito geral e alterá-lo para funcionar apenas em um caso específico que seja seriamente limitante. Isso é verdade, claro, mas inútil.

Why is not SBMM more widely used? What are its disadvantages?

O SBMM limita o que você pode fazer:

  1. O SBMM cria o problema de funeral ascendente com fechamentos lexicais de primeira classe, razão pela qual os fechamentos são populares e fáceis para usar em linguagens como C #, mas raro e complicado em C ++. Observe que há uma tendência geral para o uso de construções funcionais na programação.

  2. O SBMM requer destruidores e eles impedem chamadas finais adicionando mais trabalho para fazer antes que uma função possa retornar. As chamadas finais são úteis para máquinas de estado extensíveis e são fornecidas por coisas como o .NET.

  3. Algumas estruturas de dados e algoritmos são notoriamente difíceis de implementar usando o SBMM. Basicamente, em qualquer lugar, os ciclos ocorrem naturalmente. Mais notavelmente algoritmos gráficos. Você efetivamente acaba escrevendo seu próprio GC.

  4. A programação simultânea é mais difícil porque o fluxo de controle e, portanto, a vida útil do objeto são inerentemente não-determinísticos aqui. Soluções práticas em sistemas de transmissão de mensagens tendem a ser uma cópia profunda das mensagens e o uso de vidas excessivamente longas.

  5. O SBMM mantém objetos vivos até o final de seu escopo no código-fonte, que geralmente é mais longo do que o necessário e pode ser muito mais longo do que o necessário. Isso aumenta a quantidade de lixo flutuante (objetos inacessíveis esperando para serem reciclados). Em contraste, rastrear a coleta de lixo tende a liberar objetos logo após a última referência a eles desaparecer, o que pode ser muito mais cedo. Consulte Mitos de gerenciamento de memória: rapidez .

O SBMM é tão limitante que os programadores precisam de uma rota de escape para situações em que a vida útil não pode ser feita para aninhar. Em C ++, shared_ptr oferece uma rota de fuga, mas pode ser ~ 10x mais lento que rastrear a coleta de lixo . Portanto, usar o SBMM em vez do GC colocaria a maioria das pessoas erradas na maior parte do tempo. Isso não quer dizer, no entanto, que é inútil. O SBMM ainda é valioso no contexto de sistemas e programação incorporada, onde os recursos são limitados.

FWIW, você pode querer dar uma olhada em Forth e Ada, e ler sobre o trabalho de Nicolas Wirth.

    
por 13.01.2015 / 08:10
fonte
4

Olhando para algum índice de popularidade como TIOBE (o que é discutível, é claro, mas acho que para o seu tipo de pergunta é ok usar isso), você primeiro vê que ~ 50% dos 20 maiores são "linguagens de script" ou "Dialetos SQL", onde a "facilidade de uso" e os meios de abstração têm uma importância muito maior do que o comportamento determinístico. Dos restantes idiomas "compilados", existem cerca de 50% dos idiomas com SBMM e ~ 50% sem. Então, ao tirar as linguagens de script do seu cálculo, eu diria que sua suposição é apenas errada, entre as linguagens compiladas, aquelas com SBMM são tão populares quanto as que não possuem.

    
por 09.03.2014 / 15:11
fonte
3

Uma grande vantagem de um sistema de GC que ninguém mencionou ainda é que uma referência em um sistema de GC tem a garantia de manter sua identidade enquanto existir . Se alguém chamar IDisposable.Dispose (.NET) ou AutoCloseable.Close (Java) em um objeto enquanto houver cópias da referência, essas cópias continuarão a se referir ao mesmo objeto. O objeto não será mais útil para nada, mas as tentativas de usá-lo terão um comportamento previsível controlado pelo próprio objeto. Por outro lado, em C ++, se o código chamar delete em um objeto e depois tentar usá-lo, o estado inteiro do sistema se tornará totalmente indefinido.

Outra coisa importante a notar é que o gerenciamento de memória baseado em escopo funciona muito bem para objetos com propriedade claramente definida. Funciona muito menos e às vezes de maneira ruim com objetos que não possuem propriedade definida. Em geral, os objetos mutáveis devem ter proprietários, enquanto os objetos imutáveis não precisam, mas há um problema: é muito comum que o código use uma instância de um tipo mutável para manter dados imutáveis, garantindo que nenhuma referência seja exposta a código que pode alterar a instância. Nesse cenário, instâncias da classe mutável podem ser compartilhadas entre vários objetos imutáveis e, portanto, não possuem uma propriedade clara.

    
por 10.03.2014 / 01:11
fonte
-2
Primeiro, é muito importante perceber que equalizar RAII para SBMM. ou até mesmo para o SBRM. Uma das qualidades mais essenciais (e menos conhecidas ou mais subestimadas) do RAII é o fato de que "tornar-se um recurso" é uma propriedade que NÃO é transitiva para a composição.

O seguinte post no blog discute este aspecto importante do RAII e o contrasta com o gerenciamento de recursos em linguagens GCed que usam GC não determinístico.

link

É importante notar que enquanto o RAII é usado principalmente em C ++, o Python (finalmente a versão não baseada em VM) possui destruidores e CG determinístico que permite que o RAII seja usado junto com o GC. Melhor dos dois mundos se fosse.

    
por 15.03.2014 / 00:27
fonte