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


108

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

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

Compilado 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á compilado solo para x86 :

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

Luego probé el siguiente programa, 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 programa tan simple. ¿Hay algún truco para hacer que un proceso de C # sea un poco más sencillo? Estoy escribiendo un programa que está diseñado para ejecutarse en segundo plano la mayor parte del tiempo. Ya estoy haciendo las cosas de la 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á en segundo plano parece excesivo.

PD En cuanto a por qué me importaría --- los usuarios (avanzados) tienden a preocuparse por estas cosas. Incluso si casi no tiene ningún efecto en el rendimiento, los usuarios semi-conocedores de la tecnología (mi público objetivo) tienden a entrar en arrebatos sobre el uso de la memoria de la aplicación en segundo plano. Incluso me asusto cuando veo que Adobe Updater ocupa 11 MB de memoria y me tranquiliza el toque relajante de Foobar2000, que puede ocupar menos de 6 MB incluso cuando se reproduce. Sé que en los sistemas operativos modernos, estas cosas realmente no importan mucho técnicamente, pero eso no significa que no afecten la percepción.


14
¿Por qué te importaría? El conjunto de trabajo privado es bastante bajo. Los sistemas operativos modernos aparecerán en el disco si la memoria no es necesaria. Es 2009. A menos que esté creando cosas en sistemas integrados, no debería preocuparse por los 10 MB.
Mehrdad Afshari

8
Deje de usar .NET y podrá tener pequeños programas. Para cargar el marco .NET, es necesario cargar en la memoria muchas DLL grandes.
Adam Sills

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

28
@LeakyCode Realmente odio que los programadores modernos piensen de esta manera, debería preocuparse por el uso de memoria de su aplicación. Puedo decir que la mayoría de las aplicaciones modernas, escritas principalmente en java o c # son bastante ineficaces en lo que respecta a la gestión de recursos, y gracias a eso en 2014 podemos ejecutar tantas aplicaciones como pudimos en 1998 en win95 y 64mb de ram. ... solo 1 instancia de navegador ahora consume 2 GB de RAM y un IDE simple de aproximadamente 1 GB. La RAM es barata, pero eso no significa que debas desperdiciarla.
Petr

6
@Petr Debería preocuparse por la gestión de recursos. El tiempo del programador también es un recurso. No generalice en exceso el desperdicio de 10 MB a 2 GB.
Mehrdad Afshari

Respuestas:


33
  1. Es posible que desee consultar la huella de memoria de .NET EXE de la pregunta de desbordamiento de pila .
  2. La publicación del blog de MSDN Working set! = Actual memory footprint se trata de desmitificar el conjunto de trabajo, procesar la memoria y cómo realizar cálculos precisos sobre el consumo total de RAM.

No diré que deba ignorar la huella de memoria de su aplicación; obviamente, lo más pequeño y eficiente tiende a ser deseable. Sin embargo, debe considerar cuáles son sus necesidades reales.

Si está escribiendo una aplicación cliente 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 siendo más descuidado con la asignación de memoria. (Siempre que se desasigne todo).

Sin embargo, para dirigirme a algunas personas aquí que dicen que no se preocupen por eso: 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í , debe considerar absolutamente el uso de la memoria. Y tendrás que estar atento. La mejor manera de abordar esto es con un buen diseño de estructura de datos y siguiendo las mejores prácticas con respecto a cuándo y qué asigna.


45

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 quieres algo realmente ordenado, es posible que .NET no sea la mejor opción.

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

Si desea mantener una 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 tiempo del necesario.
  • Tenga en cuenta los List<T>tipos similares que duplican la capacidad cuando es necesario, ya que pueden generar hasta un 50% de desperdicio.
  • Podría considerar usar tipos de valor en lugar de 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 de ninguna manera, sino solo un par de ideas.


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

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

17

Una cosa que debe considerar en este caso es el costo de memoria del CLR. El CLR se carga para cada proceso .Net y, por lo tanto, influye en las consideraciones de memoria. Para un programa tan simple / pequeño, el costo de CLR dominará su huella de memoria.

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


7

No hay sugerencias específicas per se, pero puede echar un vistazo a CLR Profiler (descarga gratuita de Microsoft).
Una vez que lo haya instalado, eche un vistazo a esta página de instrucciones .

Desde el cómo hacerlo:

Este Cómo le muestra cómo utilizar la herramienta CLR Profiler para investigar el perfil de asignación de memoria de su aplicación. Puede usar 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.


7

Es posible que desee ver el uso de 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 programa, pero el consumo de memoria será mucho más razonable después de ese punto.


4

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

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

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


2

Hay muchas formas de reducir su huella.

Una cosa con la que siempre tendrás que vivir 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 completamente entre instancias de aplicaciones. Incluso los ensamblajes NGEN'ed no son completamente estáticos, todavía tienen algunas partes pequeñas que necesitan ser ajustadas.

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

Un ejemplo que se ve a menudo: tomar un lector de datos, cargar el contenido en una tabla de datos solo para escribirlo en un archivo XML. Puede ejecutar fácilmente una excepción OutOfMemoryException. OTOH, puede usar un XmlTextWriter y desplazarse por el lector de datos, emitiendo XmlNodes mientras se desplaza por el cursor de la base de datos. De esa manera, solo tendrá el registro de la base de datos actual y su salida XML en la memoria. Que nunca (o es poco probable que lo haga) obtendrá una generación de recolección de basura superior y, por lo tanto, se puede reutilizar.

Lo mismo se aplica a obtener una lista de algunas instancias, hacer algunas cosas (que generan miles de instancias nuevas, que pueden permanecer referenciadas en algún lugar), y aunque no las necesite después, seguirá haciendo referencia a todo hasta después del foreach. Anular explícitamente su lista de entrada y sus subproductos temporales significa que esta memoria se puede reutilizar incluso antes de salir de su ciclo.

C # tiene una característica excelente llamada iteradores. Le permiten transmitir objetos desplazándose por su entrada y solo mantienen la instancia actual hasta que obtenga la siguiente. Incluso al usar LINQ, no es necesario que lo guarde todo solo porque desea que se filtre.


1

Abordar 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 2xN de dobles) y solo se necesita una pequeña fracción, entonces se puede escribir un componente COM contenedor que oculta la memoria de .NET y devuelve solo los datos que son necesario.

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


0

Descubrí que el uso de las API SetProcessWorkingSetSize o EmptyWorkingSet para forzar las páginas de memoria al disco periódicamente en un proceso de ejecución prolongada puede hacer que toda la memoria física disponible en una máquina desaparezca efectivamente hasta que se reinicie la máquina. 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 de memoria intensiva. Descubrí que después de entre 1 día y una semana, una máquina mostraría un uso de memoria física del 99% en el Administrador de tareas, mientras que no se demostró que ningún proceso utilizara un uso significativo de memoria. Poco después, la máquina dejaba de responder, lo que requería un reinicio completo. Dichas máquinas eran más de dos docenas de servidores Windows Server 2008 R2 y 2012 R2 que se ejecutaban tanto en hardware físico como virtual.

Quizás 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. Quizás solo lo use una vez después del lanzamiento inicial de su aplicación. He decidido deshabilitar el código y dejar que Garbage Collector administre el uso de la memoria por sí solo.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.