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.
fonte
Respostas:
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.
fonte
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:
List<T>
tipos semelhantes que dobram a capacidade quando necessário, pois podem levar a até 50% de desperdício.Provavelmente, essa não é uma lista exaustiva, mas apenas algumas idéias.
fonte
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.
fonte
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:
fonte
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.
fonte
Ainda há maneiras de reduzir o conjunto de trabalho privado deste programa simples:
NGEN seu aplicativo. Isso remove o custo de compilação JIT de seu processo.
Treine seu aplicativo usando MPGO reduzindo o uso de memória e, em seguida, NGEN-lo.
fonte
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.
fonte
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.
fonte
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.
fonte