¿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.
fuente
Respuestas:
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.
fuente
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:
List<T>
tipos similares que duplican la capacidad cuando es necesario, ya que pueden generar hasta un 50% de desperdicio.Probablemente no sea una lista exhaustiva de ninguna manera, sino solo un par de ideas.
fuente
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.
fuente
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:
fuente
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.
fuente
Todavía hay formas de reducir el conjunto de trabajo privado de este sencillo programa:
NGEN su aplicación. Esto elimina el costo de compilación JIT de su proceso.
Entrene su aplicación usando MPGO reduciendo el uso de memoria y luego NGEN.
fuente
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.
fuente
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.
fuente
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.
fuente