Pergunta

Ocasionalmente, temos tido problemas em que nossos processos de servidor de longa execução (em execução no Windows Server 2003) geraram uma exceção devido a uma falha na alocação de memória.Nossa suspeita é que essas alocações estejam falhando devido à fragmentação da memória.

Portanto, estamos analisando alguns mecanismos alternativos de alocação de memória que podem nos ajudar e espero que alguém possa me dizer qual é o melhor:

1) Utilize o Windows Pilha de baixa fragmentação

2) jemalloc - conforme usado em Firefox 3

3) Doug Lea maloc

Nosso processo de servidor é desenvolvido usando código C++ multiplataforma, portanto, qualquer solução seria idealmente multiplataforma também (os sistemas operacionais *nix sofrem com esse tipo de fragmentação de memória?).

Além disso, estou certo ao pensar que LFH é agora o mecanismo de alocação de memória padrão para o Windows Server 2008/Vista?Meus problemas atuais "desaparecerão" se nossos clientes simplesmente atualizarem o sistema operacional de seus servidores?

Foi útil?

Solução

Primeiro, concordo com os outros autores que sugeriram um vazamento de recursos.Você realmente quer descartar isso primeiro.

Esperançosamente, o gerenciador de heap que você está usando atualmente tem uma maneira de despejar o espaço livre total real disponível no heap (em todos livre blocos) e também o número total de blocos em que está dividido.Se o tamanho médio do bloco livre for relativamente pequeno comparado ao espaço livre total no heap, você terá um problema de fragmentação.Alternativamente, se você puder descartar o tamanho do maior bloco livre e compará-lo com o espaço livre total, isso resultará na mesma coisa.O maior bloco livre seria pequeno em relação ao total livre espaço disponível em todos os blocos se você estiver enfrentando fragmentação.

Para ser muito claro sobre o que foi dito acima, em todos os casos estamos falando de livre blocos no heap, não os blocos alocados no heap.De qualquer forma, se as condições acima não forem atendidas, você fazer tem algum tipo de situação de vazamento.

Portanto, depois de descartar um vazamento, você poderá considerar o uso de um alocador melhor. Malloc de Doug Lea sugerido na pergunta é um alocador muito bom para aplicativos de uso geral e muito robusto maioria do tempo.Dito de outra forma, foi testado ao longo do tempo para funcionar muito bem para quase todas as aplicações.No entanto, nenhum algoritmo é ideal para todos aplicações e qualquer abordagem de algoritmo de gerenciamento podem ser quebradas pelas condições patológicas corretas em relação ao seu design.

Por que você está tendo um problema de fragmentação? - Fontes de problemas de fragmentação são causado pelo comportamento de um aplicativo e tem a ver com tempos de vida de alocação muito diferentes na mesma área de memória.Isto é, alguns objetos são alocados e liberados regularmente, enquanto outros tipos de objetos persistem por longos períodos de tempo, todos na mesma pilha... pense nos de vida mais longa como abrindo buracos em áreas maiores da arena e, assim, evitando o coalescer de blocos adjacentes que foram liberados.

Para resolver esse tipo de problema, a melhor coisa que você pode fazer é dividir logicamente o heap em subáreas onde os tempos de vida são mais semelhantes.Na verdade, você deseja um heap transitório e um heap ou heaps persistentes que agrupem coisas com tempos de vida semelhantes.

Alguns outros sugeriram outra abordagem para resolver o problema, que é tentar tornar os tamanhos de alocação mais semelhantes ou idênticos, mas isso é menos ideal porque cria um tipo diferente de fragmentação chamada fragmentação interna - que é, na verdade, o espaço desperdiçado que você tem alocando mais memória no bloco do que você precisa.

Além disso, com um bom alocador de heap, como o de Doug Lea, tornar os tamanhos dos blocos mais semelhantes é desnecessário porque o alocador já estará executando um esquema de agrupamento de dois tamanhos que tornará completamente desnecessário ajustar artificialmente os tamanhos de alocação passados ​​​​para malloc( ) - na verdade, seu gerenciador de heap faz isso automaticamente para você de forma muito mais robusta do que o aplicativo será capaz de fazer ajustes.

Outras dicas

Acho que você descartou erroneamente um vazamento de memória muito cedo.Mesmo um pequeno vazamento de memória pode causar grave fragmentação de memória.

Supondo que seu aplicativo se comporte da seguinte forma:
Alocar 10 MB
Alocar 1 byte
10 MB grátis
(ops, não liberamos 1 byte, mas quem se importa com 1 byte minúsculo)

Parece um vazamento muito pequeno, você dificilmente notará isso ao monitorar apenas o tamanho total da memória alocada.Mas esse vazamento eventualmente fará com que a memória do seu aplicativo fique assim:
.
.
Grátis – 10 MB
.
.
[Alocado -1 byte]
.
.
Grátis – 10 MB
.
.
[Alocado -1 byte]
.
.
Grátis – 10 MB
.
.

Esse vazamento não será notado...até que você queira alocar 11 MB
Supondo que seus minidespejos incluíssem informações completas de memória, recomendo usar DebugDiag para detectar possíveis vazamentos.No relatório de memória gerado, examine cuidadosamente a contagem de alocação (não o tamanho).

Como você sugere, o malloc de Doug Lea pode funcionar bem.É multiplataforma e tem sido usado no código de remessa.No mínimo, deve ser fácil integrá-lo ao seu código para teste.

Tendo trabalhado em ambientes de memória fixa durante vários anos, esta situação é certamente um problema, mesmo em ambientes não fixos.Descobrimos que os alocadores CRT tendem a ser muito ruins em termos de desempenho (velocidade, eficiência de espaço desperdiçado, etc.).Acredito firmemente que se você precisa muito de um bom alocador de memória por um longo período de tempo, você deve escrever o seu próprio (ou ver se algo como dlmalloc funcionará).O truque é escrever algo que funcione com seus padrões de alocação e que tenha mais a ver com a eficiência do gerenciamento de memória do que com quase qualquer outra coisa.

Experimente o dlmalloc.Eu definitivamente dou um joinha.Também é bastante ajustável, portanto, você poderá obter mais eficiência alterando algumas das opções de tempo de compilação.

Honestamente, você não deve depender de que as coisas “desapareçam” com novas implementações de sistema operacional.Um service pack, patch ou outro novo sistema operacional anos depois pode piorar o problema.Novamente, para aplicativos que exigem um gerenciador de memória robusto, não use as versões padrão disponíveis em seu compilador.Encontre um que funcione para seu situação.Comece com dlmalloc e ajuste-o para ver se consegue obter o comportamento que funciona melhor para sua situação.

Você pode ajudar a reduzir a fragmentação reduzindo a quantidade alocada desalocada.

por exemplo.digamos, para um servidor web executando um script do lado do servidor, ele pode criar uma string para a qual a página será gerada.Em vez de alocar e desalocar essas strings para cada solicitação de página, apenas mantenha um conjunto delas, para que você só aloque quando precisar de mais, mas não desaloque (ou seja, depois de um tempo você terá a situação de não alocar mais, porque você tem suficiente)

Você pode usar _CrtDumpMemoryLeaks();para despejar vazamentos de memória na janela de depuração ao executar uma compilação de depuração, no entanto, acredito que isso seja específico do compilador Visual C.(está em crtdbg.h)

Eu suspeitaria de um vazamento antes de suspeitar de fragmentação.

Para estruturas de dados com uso intensivo de memória, você pode mudar para um mecanismo de pool de armazenamento reutilizável.Você também pode alocar mais coisas na pilha em vez de na pilha, mas em termos práticos isso não fará muita diferença, eu acho.

Eu iniciaria uma ferramenta como o valgrind ou faria algum registro intensivo para procurar recursos que não estavam sendo liberados.

@nsaners - tenho certeza que o problema está na fragmentação da memória.Nós analisamos minidespejos que apontam para um problema quando um grande pedaço de memória (5-10 MB) está sendo alocado.Também monitoramos o processo (no local e em desenvolvimento) para verificar vazamentos de memória - nenhum foi detectado (o consumo de memória geralmente é bastante baixo).

O problema acontece no Unix, embora geralmente não seja tão ruim.

A pilha de baixa estruturação nos ajudou, mas meus colegas de trabalho confiam Pilha Inteligente(ele tem sido usado em várias plataformas em alguns de nossos produtos há anos).Infelizmente, devido a outras circunstâncias, não pudemos usar o Smart Heap desta vez.

Também analisamos a alocação de blocos/chunking e tentamos ter piscinas/estratégias com conhecimento de escopo, ou seja, coisas de longo prazo aqui, coisa de solicitação inteira lá, coisas de curto prazo por lá, etc.

Como sempre, você pode desperdiçar memória para ganhar velocidade.

Essa técnica não é útil para um alocador de uso geral, mas tem seu lugar.

Basicamente, a ideia é escrever um alocador que retorne memória de um pool onde todas as alocações sejam do mesmo tamanho.Este pool nunca pode ficar fragmentado porque qualquer bloco é tão bom quanto outro.Você pode reduzir o desperdício de memória criando vários pools com pedaços de tamanhos diferentes e escolhendo o pool de tamanho de pedaço menor que ainda seja maior que a quantidade solicitada.Usei essa ideia para criar alocadores que rodam em O(1).

A solução simples, rápida e suja é dividir o aplicativo em vários processos, você deverá obter um HEAP novo sempre que criar o processo.

Sua memória e velocidade podem sofrer um pouco (troca), mas hardware rápido e grande RAM devem ajudar.

Esse era um velho truque do UNIX com daemons, quando threads ainda não existiam.

se você está falando sobre Win32 - você pode tentar espremer algo usando LARGEADDRESSAWARE.Você terá aproximadamente 1 Gb de memória desfragmentada extra para que seu aplicativo a fragmente por mais tempo.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top