Reduzindo o uso de memória de aplicativos .NET?

108

Quais são algumas dicas para reduzir o uso de memória de aplicativos .NET? Considere o seguinte programa C # simples.

class Program
{
    static void Main(string[] args)
    {
        Console.ReadLine();
    }
}

Compilado no modo de lançamento para x64 e executando fora do Visual Studio, o gerenciador de tarefas relata o seguinte:

Working Set:          9364k
Private Working Set:  2500k
Commit Size:         17480k

É um pouco melhor se for compilado apenas para x86 :

Working Set:          5888k
Private Working Set:  1280k
Commit Size:          7012k

Em seguida, tentei o seguinte programa, que faz o mesmo, mas tenta diminuir o tamanho do processo após a inicialização do tempo de execução:

class Program
{
    static void Main(string[] args)
    {
        minimizeMemory();
        Console.ReadLine();
    }

    private static void minimizeMemory()
    {
        GC.Collect(GC.MaxGeneration);
        GC.WaitForPendingFinalizers();
        SetProcessWorkingSetSize(Process.GetCurrentProcess().Handle,
            (UIntPtr) 0xFFFFFFFF, (UIntPtr)0xFFFFFFFF);
    }

    [DllImport("kernel32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool SetProcessWorkingSetSize(IntPtr process,
        UIntPtr minimumWorkingSetSize, UIntPtr maximumWorkingSetSize);
}

Os resultados na versão x86 fora do Visual Studio:

Working Set:          2300k
Private Working Set:   964k
Commit Size:          8408k

O que é um pouco melhor, mas ainda parece excessivo para um programa tão simples. Existem truques para tornar um processo C # um pouco mais enxuto? Estou escrevendo um programa projetado para ser executado em segundo plano na maior parte do tempo. Já estou fazendo qualquer coisa de interface do usuário em um domínio de aplicativo separado que significa que as coisas da interface do usuário podem ser descarregadas com segurança, mas ocupar até 10 MB quando está apenas no fundo parece excessivo.

PS: Por que eu me importaria --- Usuários (avançados) tendem a se preocupar com essas coisas. Mesmo que quase não tenha efeito sobre o desempenho, usuários semitecnológicos (meu público-alvo) tendem a ter acessos de raiva sobre o uso de memória de aplicativos em segundo plano. Até eu enlouqueço quando vejo o Adobe Updater ocupando 11 MB de memória e me sinto aliviada com o toque calmante do Foobar2000, que pode ocupar menos de 6 MB mesmo durante a reprodução. Eu sei que em sistemas operacionais modernos, essas coisas realmente não importam muito tecnicamente, mas isso não significa que não afetem a percepção.

Robert Fraser
fonte
14
Por que você se importaria? O conjunto de trabalho privado é muito baixo. Os sistemas operacionais modernos irão enviar a página para o disco se a memória for desnecessária. É 2009. A menos que você esteja construindo coisas em sistemas embarcados, você não deve se preocupar com 10 MB.
Mehrdad Afshari
8
Pare de usar o .NET e você terá pequenos programas. Para carregar o .NET framework, muitas DLLs grandes precisam ser carregadas na memória.
Adam Sills
2
Os preços da memória caem exponencialmente (sim, você pode encomendar um sistema de computador doméstico Dell com 24 GB de RAM agora). A menos que seu aplicativo esteja usando> 500 MB, a otimização é desnecessária.
Alex
28
@LeakyCode Eu realmente odeio que os programadores modernos pensem assim, você deve se preocupar com o uso da memória de seu aplicativo. Posso dizer que a maioria dos aplicativos modernos, escritos principalmente em java ou c #, são bastante ineficazes quando se trata de gerenciamento de recursos e, graças a isso, em 2014, podemos executar tantos aplicativos quanto poderíamos em 1998 em win95 e 64 MB de RAM ... apenas 1 instância do navegador agora consome 2 gb de ram e IDE simples cerca de 1 gb. Ram é barato, mas isso não significa que você deve desperdiçá-lo.
Petr
6
@Petr Você deve se preocupar com o gerenciamento de recursos. O tempo do programador também é um recurso. Não generalize demais o desperdício de 10 MB a 2 GB.
Mehrdad Afshari

Respostas:

33
  1. Você pode querer dar uma olhada na questão Stack Overflow do espaço de memória do .NET EXE .
  2. A postagem no blog do MSDN Working set! = Área de cobertura de memória real trata de desmistificar o conjunto de trabalho, processar a memória e fazer cálculos precisos sobre o consumo total de memória RAM.

Não direi que você deve ignorar o consumo de memória de seu aplicativo - obviamente, menor e mais eficiente tende a ser desejável. No entanto, você deve considerar quais são suas necessidades reais.

Se você estiver escrevendo um aplicativo cliente padrão do Windows Forms e WPF destinado a ser executado no PC de um indivíduo e provavelmente será o aplicativo principal no qual o usuário opera, será mais indiferente quanto à alocação de memória. (Contanto que tudo seja desalocado.)

No entanto, para abordar algumas pessoas aqui que dizem para não se preocupar com isso: Se você está escrevendo um aplicativo Windows Forms que será executado em um ambiente de serviços de terminal, em um servidor compartilhado possivelmente utilizado por 10, 20 ou mais usuários, então sim , você absolutamente deve considerar o uso de memória. E você precisará estar vigilante. A melhor maneira de resolver isso é com um bom design de estrutura de dados e seguindo as práticas recomendadas em relação a quando e o que você aloca.

John Rudy
fonte
45

Os aplicativos .NET terão uma pegada maior em comparação com os aplicativos nativos, devido ao fato de que ambos precisam carregar o tempo de execução e o aplicativo no processo. Se você deseja algo realmente organizado, o .NET pode não ser a melhor opção.

No entanto, lembre-se de que, se seu aplicativo estiver em hibernação, as páginas de memória necessárias ficarão sem memória e, portanto, não serão um fardo para o sistema na maior parte do tempo.

Se você quiser manter a pegada pequena, terá que pensar no uso da memória. Aqui estão algumas ideias:

  • Reduza o número de objetos e certifique-se de não segurar nenhuma instância por mais tempo do que o necessário.
  • Esteja ciente de List<T>tipos semelhantes que dobram a capacidade quando necessário, pois podem levar a até 50% de desperdício.
  • Você pode considerar o uso de tipos de valor em vez de tipos de referência para forçar mais memória na pilha, mas lembre-se de que o espaço de pilha padrão é de apenas 1 MB.
  • Evite objetos com mais de 85000 bytes, pois eles irão para o LOH que não é compactado e, portanto, podem ser facilmente fragmentados.

Provavelmente, essa não é uma lista exaustiva, mas apenas algumas idéias.

Brian Rasmussen
fonte
IOW, os mesmos tipos de técnicas que funcionam para reduzir o tamanho do código nativo funcionam no .NET?
Robert Fraser
Acho que há alguma sobreposição, mas com o código nativo você tem mais opções no que diz respeito ao uso de memória.
Brian Rasmussen
17

Uma coisa que você precisa considerar neste caso é o custo de memória do CLR. O CLR é carregado para cada processo .Net e, portanto, influencia as considerações de memória. Para um programa tão simples / pequeno, o custo do CLR vai dominar sua pegada de memória.

Seria muito mais instrutivo construir uma aplicação real e ver o custo disso em comparação com o custo deste programa de linha de base.

JaredPar
fonte
7

Sem sugestões específicas, mas você pode dar uma olhada no CLR Profiler (download gratuito da Microsoft).
Depois de instalá-lo, dê uma olhada nesta página de instruções .

Do como fazer:

Este artigo mostra como usar a ferramenta CLR Profiler para investigar o perfil de alocação de memória do aplicativo. Você pode usar o CLR Profiler para identificar o código que causa problemas de memória, como vazamentos de memória e coleta de lixo excessiva ou ineficiente.

Rosquinha
fonte
7

Talvez queira examinar o uso de memória de um aplicativo "real".

Semelhante ao Java, há uma quantidade fixa de sobrecarga para o tempo de execução, independentemente do tamanho do programa, mas o consumo de memória será muito mais razoável depois desse ponto.

Eric Petroelje
fonte
4

Ainda há maneiras de reduzir o conjunto de trabalho privado deste programa simples:

  1. NGEN seu aplicativo. Isso remove o custo de compilação JIT de seu processo.

  2. Treine seu aplicativo usando MPGO reduzindo o uso de memória e, em seguida, NGEN-lo.

Feng Yuan
fonte
2

Existem muitas maneiras de reduzir sua pegada.

Uma coisa com a qual você sempre terá que conviver no .NET é que o tamanho da imagem nativa do seu código IL é enorme

E esse código não pode ser totalmente compartilhado entre as instâncias do aplicativo. Mesmo os conjuntos NGEN não são completamente estáticos, eles ainda têm algumas pequenas peças que precisam de JIT.

As pessoas também tendem a escrever códigos que bloqueiam a memória por muito mais tempo do que o necessário.

Um exemplo frequentemente visto: pegar um Datareader, carregar o conteúdo em uma DataTable apenas para gravá-lo em um arquivo XML. Você pode facilmente encontrar uma OutOfMemoryException. OTOH, você poderia usar um XmlTextWriter e rolar pelo Datareader, emitindo XmlNodes conforme você rola pelo cursor do banco de dados. Dessa forma, você só tem o registro do banco de dados atual e sua saída XML na memória. Que nunca (ou é improvável) obterá uma geração de coleta de lixo maior e, portanto, pode ser reutilizado.

O mesmo se aplica a obter uma lista de algumas instâncias, fazer algumas coisas (que gera milhares de novas instâncias, que podem permanecer referenciadas em algum lugar), e mesmo que você não precise delas depois, ainda faz referência a tudo até depois do foreach. Anular explicitamente sua lista de entrada e seus subprodutos temporários significa que essa memória pode ser reutilizada mesmo antes de você sair do loop.

C # tem um excelente recurso chamado iteradores. Eles permitem que você transmita objetos percorrendo sua entrada e apenas mantém a instância atual até que você obtenha a próxima. Mesmo usando o LINQ, você ainda não precisa manter tudo por perto apenas porque deseja que seja filtrado.

Robert Giesecke
fonte
1

Abordando a questão geral no título e não a questão específica:

Se você estiver usando um componente COM que retorna muitos dados (digamos, grandes matrizes 2xN de duplos) e apenas uma pequena fração é necessária, então é possível escrever um componente COM wrapper que oculta a memória do .NET e retorna apenas os dados que são necessário.

Isso é o que fiz em meu aplicativo principal e melhorou significativamente o consumo de memória.

Peter Mortensen
fonte
0

Descobri que usar as APIs SetProcessWorkingSetSize ou EmptyWorkingSet para forçar páginas de memória para o disco periodicamente em um processo de longa execução pode fazer com que toda a memória física disponível em uma máquina desapareça efetivamente até que a máquina seja reinicializada. Tínhamos uma DLL .NET carregada em um processo nativo que usaria a API EmptyWorkingSet (uma alternativa ao uso de SetProcessWorkingSetSize) para reduzir o conjunto de trabalho após a execução de uma tarefa que consome muita memória. Eu descobri que depois de um dia a uma semana, uma máquina mostraria 99% de uso de memória física no Gerenciador de Tarefas, enquanto nenhum processo estava usando qualquer uso significativo de memória. Logo depois, a máquina deixava de responder, exigindo uma reinicialização forçada. Essas máquinas eram mais de 2 dúzias de servidores Windows Server 2008 R2 e 2012 R2 em execução em hardware físico e virtual.

Talvez ter o código .NET carregado em um processo nativo tenha algo a ver com isso, mas use EmptyWorkingSet (ou SetProcessWorkingSetSize) por sua própria conta e risco. Talvez só use uma vez após o lançamento inicial de seu aplicativo. Decidi desabilitar o código e deixar o Coletor de Lixo gerenciar o uso de memória por conta própria.

Stuart Welch
fonte