2009-08-27 10 views
102

¿Cuáles son algunos consejos para reducir el uso de memoria de las aplicaciones .NET? Considere el siguiente programa sencillo de C#.¿Reduciendo el uso de memoria de las aplicaciones .NET?

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

Elaborado de liberación modo para x64 y se ejecuta fuera de Visual Studio, el administrador de tareas informa lo siguiente:

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

Es un poco mejor si es compilado sólo para x 86:

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

Luego intenté th e siguiente programa, que hace lo mismo, pero trata de recortar el tamaño de proceso después de la inicialización en tiempo de ejecución:

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); 
} 

Los resultados en x 86lanzamiento fuera de Visual Studio:

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

que es un poco mejor , pero aún parece excesivo para un programa tan simple. ¿Hay algún truco para hacer un proceso de C# un poco más delgado? Estoy escribiendo un programa que está diseñado para ejecutarse en segundo plano la mayor parte del tiempo. Ya estoy haciendo cosas de interfaz de usuario en un Application Domain por separado, lo que significa que las cosas de la interfaz de usuario se pueden descargar de forma segura, pero ocupar 10   MB cuando está sentado en segundo plano parece excesivo.

P.S. En cuanto a por qué me importaría --- (Poder) los usuarios tienden a preocuparse por estas cosas. Incluso si no tiene casi ningún efecto sobre el rendimiento, los usuarios expertos en semi-tecnología (mi público objetivo) tienden a tener ataques de histeria por el uso de la memoria de aplicaciones en segundo plano. Incluso me asusto cuando veo que Adobe Updater toma 11   MB de memoria y me alivia el tacto tranquilizador de Foobar2000, que puede tomar menos de 6   MB incluso cuando se reproduce. Sé que en los sistemas operativos modernos, estas cosas realmente no importan mucho desde el punto de vista técnico, pero eso no significa que no afecten la percepción.

+13

¿Por qué te importa? El conjunto de trabajo privado es bastante bajo. Los SO modernos se enviarán al disco si la memoria no es necesaria. Es 2009. A menos que estés construyendo cosas en sistemas integrados, no deberías preocuparte por 10MB. –

+8

Deja de usar .NET y puedes tener pequeños programas. Para cargar .NET Framework, es necesario cargar muchas DLL grandes en la memoria. –

+2

Los precios de la memoria caen exponencialmente (sí, puede solicitar un sistema de computadora doméstico Dell con 24 GB de RAM ahora). A menos que su aplicación esté usando> 500MB la optimización no es necesaria. – Alex

Respuesta

33
  1. Es posible que desee echa un vistazo a desbordamiento de pila pregunta .NET EXE memory footprint.
  2. La publicación de blog de MSDN Working set != actual memory footprint se trata de desmitificar el conjunto de trabajo, la memoria de proceso y cómo realizar cálculos precisos sobre el consumo total en RAM.

No diré que debe ignorar la huella de memoria de su aplicación; obviamente, tiende a ser deseable una menor y más eficiente. Sin embargo, debes considerar cuáles son tus necesidades reales.

Si está escribiendo una aplicación estándar de Windows Forms y WPF que está destinada a ejecutarse en la PC de un individuo y es probable que sea la aplicación principal en la que opera el usuario, puede salirse con la suya con la memoria asignación. (Mientras todo se desasigna.)

Sin embargo, para dirigirse a algunas personas aquí que dicen que no se preocupen: si está escribiendo una aplicación de Windows Forms que se ejecutará en un entorno de servicios de terminal, en un Servidor compartido posiblemente utilizado por 10, 20 o más usuarios, entonces sí, absolutamente debe considerar el uso de la memoria. Y tendrás que estar atento. La mejor manera de abordar esto es con un buen diseño de la estructura de datos y siguiendo las mejores prácticas con respecto a cuándo y qué asignas.

7

No hay sugerencias específicas per se, pero puede echarle un vistazo al CLR Profiler (descarga gratuita de Microsoft).
Una vez que lo haya instalado, eche un vistazo a este how-to page.

Desde el cómo-a:

Este Cómo se muestra cómo utilizar la herramienta CLR Profiler para investigar la asignación de memoria el perfil de su aplicación . Puede utilizar CLR Profiler para identificar el código que causa problemas de memoria , como pérdidas de memoria y basura excesiva o ineficaz colección.

41

. Las aplicaciones .NET tendrán una huella más grande en comparación con las aplicaciones nativas debido al hecho de que ambas tienen que cargar el tiempo de ejecución y la aplicación en el proceso. Si desea algo realmente ordenado, .NET puede no ser la mejor opción.

Sin embargo, tenga en cuenta que si su aplicación está durmiendo en su mayoría, las páginas de memoria necesarias se perderán de memoria y, por lo tanto, no representarán una carga para el sistema en general la mayor parte del tiempo.

Si desea mantener la huella pequeña, tendrá que pensar en el uso de la memoria. Aquí hay un par de ideas:

  • Reduce el número de objetos y asegúrate de no aferrarte a ninguna instancia más larga de la requerida.
  • Tenga en cuenta List<T> y tipos similares que duplican la capacidad cuando es necesario, ya que pueden conducir a un 50% de desperdicio.
  • Podría considerar el uso de tipos de valores sobre tipos de referencia para forzar más memoria en la pila, pero tenga en cuenta que el espacio de pila predeterminado es de solo 1 MB.
  • Evite objetos de más de 85000 bytes, ya que irán a LOH que no está compactado y por lo tanto pueden fragmentarse fácilmente.

Probablemente no sea una lista exhaustiva, pero solo un par de ideas.

+0

IOW, el mismo tipo de técnicas que funcionan para reducir el trabajo de tamaño de código nativo en .NET? –

+0

Supongo que hay una superposición, pero con el código nativo tiene más opciones cuando se trata de usar la memoria. –

16

Una cosa que debes tener en cuenta en este caso es el costo de la memoria del CLR. El CLR se carga para cada proceso .Net y, por lo tanto, tiene en cuenta las consideraciones de memoria. Para un programa tan simple/pequeño, el costo del CLR va a dominar su huella de memoria.

Sería mucho más instructivo construir una aplicación real y ver el costo de la misma en comparación con el costo de este programa de referencia.

+0

+1 por la sugerencia de construir una aplicación real! – RichardOD

6

Puede que desee ver el uso de memoria de una aplicación "real".

Al igual que en Java, existe una cantidad fija de sobrecarga para el tiempo de ejecución independientemente del tamaño del programa, pero el consumo de memoria será mucho más razonable después de ese punto.

1

Abordar la cuestión general en el título y no a la pregunta específica :

Si utiliza un componente COM que devuelve una gran cantidad de datos (dicen grandes conjuntos 2xN de dobles) y sólo una pequeña fracción es necesario, entonces uno puede escribir un componente COM contenedor que oculta la memoria de .NET y devuelve solo los datos que es necesario.

Eso es lo que hice en mi aplicación principal y mejoró significativamente el consumo de memoria.

2

Hay muchas maneras de reducir su huella.

Una cosa que siempre tenga que vivir con en .NET es que el tamaño de la imagen nativa de su código IL es enorme

Y este código no puede ser compartida por completo entre las instancias de la aplicación. Incluso los ensamblajes NGEN'ed no son completamente estáticos, todavía tienen algunas piezas pequeñas que necesitan JITting.

Las personas también tienden a escribir código que bloquea la memoria mucho más tiempo de lo necesario.

Un ejemplo que se ve a menudo: tomar un Datareader, cargar los contenidos en una DataTable solo para escribirlos en un archivo XML. Puede ejecutar fácilmente una OutOfMemoryException. OTOH, podría usar un XmlTextWriter y desplazarse por el Datareader, emitiendo XmlNodes a medida que se desplaza por el cursor de la base de datos. De esta manera, solo tiene el registro de la base de datos actual y su salida XML en la memoria. Que nunca (o es improbable que) obtenga una mayor generación de recolección de basura y, por lo tanto, puede reutilizarse.

Lo mismo se aplica a obtener una lista de algunas instancias, hacer algunas cosas (que generan miles de instancias nuevas, que pueden seguir siendo referenciadas en alguna parte), y aunque no las necesite después, todavía hace referencia a todo hasta después del foreach. Anular explícitamente su lista de entrada y sus productos de subproductos temporales, esta memoria puede reutilizarse incluso antes de salir de su ciclo.

C# tiene una función excelente llamada iteradores. Le permiten transmitir objetos al desplazarse por su entrada y solo conservar la instancia actual hasta que obtenga la siguiente. Incluso al usar LINQ, no es necesario que lo mantenga todo solo porque quería que se filtrara.

4

Todavía hay maneras de reducir el conjunto de trabajo privada de este sencillo programa:

  1. NGEN su aplicación. Esto elimina el costo de compilación de JIT de su proceso.

  2. Entrene su aplicación usando MPGO reducing memory usage y luego NGEN.

0

He encontrado que el uso de la SetProcessWorkingSetSize o EmptyWorkingSet API para obligar a las páginas de memoria en el disco periódicamente en un proceso de larga duración puede dar lugar a toda la memoria física disponible en una máquina a desaparecer de manera efectiva hasta que la máquina se reinicia. Teníamos una DLL .NET cargada en un proceso nativo que usaría la API EmptyWorkingSet (una alternativa al uso de SetProcessWorkingSetSize) para reducir el conjunto de trabajo después de realizar una tarea que requiera mucha memoria.Descubrí que, después de entre 1 día y una semana, una máquina mostraba un 99% de uso de memoria física en el Administrador de tareas, mientras que no se demostraba que los procesos utilizaran un uso de memoria significativo. Poco después la máquina dejaría de responder, lo que requeriría un reinicio duro. Dichas máquinas tenían más de 2 docenas de servidores Windows Server 2008 R2 y 2012 R2 que se ejecutaban en hardware físico y virtual.

Quizás tener el código .NET cargado en un proceso nativo tenga algo que ver con eso, pero use EmptyWorkingSet (o SetProcessWorkingSetSize) bajo su propia cuenta y riesgo. Tal vez solo lo use una vez después del lanzamiento inicial de su aplicación. Decidí desactivar el código y dejar que Garbage Collector administre el uso de la memoria por sí mismo.

Cuestiones relacionadas