¿Reduciendo el uso de memoria de las aplicaciones .NET?

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

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

Comstackdo en modo de lanzamiento para x64 y ejecutándose 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 está comstackdo solo para x86 :

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

Luego probé el siguiente progtwig, que hace lo mismo pero intenta recortar el tamaño del proceso después de la inicialización del 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 la versión x86 fuera de Visual Studio:

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

Lo cual es un poco mejor, pero aún parece excesivo para un progtwig tan simple. ¿Hay algún truco para hacer un proceso de C # un poco más delgado? Estoy escribiendo un progtwig que está diseñado para ejecutarse en segundo plano la mayor parte del tiempo. Ya estoy haciendo cosas de interfaz de usuario en un dominio de aplicación 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.

PD 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 objective) 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 se siente apaciguado por 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.

  1. Es posible que desee comprobar la pregunta de Stack Overflow .NET EXE de la huella de memoria .
  2. La publicación de blog de MSDN ¡Juego de trabajo! = La huella de memoria real 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, las más pequeñas y eficientes tienden a ser deseables. 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 ausencia de dudas sobre la asignación de memoria. (Mientras todo se desasigne).

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í , debes 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.

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 intercambiarán de la memoria y, por lo tanto, no representarán una gran 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:

  • Reduzca la cantidad de objetos y asegúrese de no retener ninguna instancia más larga de la requerida.
  • Tenga en cuenta la List y tipos similares que duplican la capacidad cuando es necesario, ya que pueden generar hasta un 50% de desperdicio.
  • Podría considerar usar tipos de valores sobre tipos de referencia para forzar más memoria en la stack, pero tenga en cuenta que el espacio de stack 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 esa no sea una lista exhaustiva, sino solo un par de ideas.

Una cosa que debes tener en cuenta en este caso es el costo de memoria del CLR. El CLR se carga para cada proceso .Net y, por lo tanto, tiene en cuenta las consideraciones de memoria. Para un progtwig 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 progtwig de referencia.

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 esta página práctica .

Desde el cómo hacerlo:

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

Puede que desee ver el uso de la memoria de una aplicación “real”.

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

Todavía hay formas de reducir el conjunto de trabajo privado de este sencillo progtwig:

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

  2. Entrene su aplicación usando MPGO reduciendo el uso de memoria y luego NGENELO.

Hay muchas formas de reducir su huella.

Una cosa con la que siempre tendrás que convivir en .NET es que el tamaño de la imagen nativa de tu código IL es enorme

Y este código no se puede compartir por completo entre las instancias de la aplicación. Incluso los ensambles NGEN no son completamente estáticos, todavía tienen algunas partes pequeñas que necesitan JITting.

La gente también tiende a escribir código que bloquea la memoria mucho más de lo necesario.

Un ejemplo a menudo visto: tomar un Datareader, cargar los contenidos en una DataTable solo para escribirlo 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 forma, 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 algún lugar), 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 subproductos temporales significa que esta memoria puede reutilizarse incluso antes de salir de su ciclo.

C # tiene una característica 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.

Abordando la pregunta general en el título y no la pregunta específica:

Si está utilizando un componente COM que devuelve una gran cantidad de datos (por ejemplo, matrices grandes de dobles 2xN) y solo se necesita una pequeña fracción, entonces se puede escribir un componente COM contenedor que oculte la memoria de .NET y devuelva solo los datos que están necesario.

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

Descubrí que el uso de las API SetProcessWorkingSetSize o EmptyWorkingSet para forzar las páginas de memoria al disco de forma periódica en un proceso prolongado puede hacer que toda la memoria física disponible en una máquina desaparezca de manera efectiva hasta que la máquina se reinicie. 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.

Tal vez tener el código .NET cargado en un proceso nativo tuvo algo que ver con eso, pero use EmptyWorkingSet (o SetProcessWorkingSetSize) bajo su propio 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.