Ridurre l'utilizzo della memoria delle applicazioni .NET?


108

Quali sono alcuni suggerimenti per ridurre l'utilizzo della memoria delle applicazioni .NET? Considera il seguente semplice programma C #.

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

Compilato in modalità di rilascio per x64 e in esecuzione all'esterno di Visual Studio, il task manager riporta quanto segue:

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

È un po 'meglio se è compilato solo per x86 :

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

Ho quindi provato il seguente programma, che fa lo stesso ma cerca di ridurre le dimensioni del processo dopo l'inizializzazione del runtime:

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

I risultati sulla versione x86 al di fuori di Visual Studio:

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

Che è un po 'meglio, ma sembra comunque eccessivo per un programma così semplice. Esistono trucchi per rendere un processo C # un po 'più snello? Sto scrivendo un programma progettato per funzionare in background la maggior parte del tempo. Sto già facendo qualsiasi cosa dell'interfaccia utente in un dominio dell'applicazione separato, il che significa che le cose dell'interfaccia utente possono essere scaricate in sicurezza, ma occupare 10 MB quando è seduto in background sembra eccessivo.

PS Quanto al motivo per cui mi interesserebbe --- Gli utenti (esperti) tendono a preoccuparsi di queste cose. Anche se non ha quasi alcun effetto sulle prestazioni, gli utenti semi-esperti di tecnologia (il mio pubblico di destinazione) tendono ad andare in crisi sull'utilizzo della memoria dell'applicazione in background. Anche io impazzisco quando vedo Adobe Updater prendere 11 MB di memoria e mi sento confortato dal tocco rilassante di Foobar2000, che può richiedere meno di 6 MB anche durante il gioco. So che nei sistemi operativi moderni questa roba non ha molta importanza tecnicamente, ma ciò non significa che non abbia un effetto sulla percezione.


14
Perché te ne importa? Il set di lavoro privato è piuttosto basso. I sistemi operativi moderni eseguiranno il paging su disco se la memoria non è necessaria. È il 2009. A meno che tu non stia costruendo cose su sistemi embedded, non dovresti preoccuparti di 10 MB.
Mehrdad Afshari,

8
Smetti di usare .NET e puoi avere piccoli programmi. Per caricare il framework .NET, è necessario caricare in memoria molte DLL di grandi dimensioni.
Adam Sills,

2
I prezzi della memoria scendono in modo esponenziale (sì, ora puoi ordinare un computer domestico Dell con 24 GB di RAM). A meno che la tua applicazione non utilizzi> 500 MB, l'ottimizzazione non è necessaria.
Alex

28
@LeakyCode Odio davvero che i programmatori moderni la pensino in questo modo, dovresti preoccuparti dell'utilizzo della memoria della tua applicazione. Posso dire che la maggior parte delle applicazioni moderne, scritte principalmente in java o c # sono piuttosto inefficaci quando si tratta di gestione delle risorse, e grazie a ciò nel 2014 possiamo eseguire quante più applicazioni potevamo nel 1998 su win95 e 64 MB di ram ... solo 1 istanza del browser ora mangia 2 GB di RAM e un semplice IDE di circa 1 GB. La ram è economica, ma questo non significa che dovresti sprecarla.
Petr,

6
@Petr Dovresti preoccuparti della gestione delle risorse. Anche il tempo del programmatore è una risorsa. Si prega di non generalizzare eccessivamente sprecando 10 MB a 2 GB.
Mehrdad Afshari

Risposte:


33
  1. Potresti voler controllare la domanda di overflow dello stack . Impronta di memoria .NET EXE .
  2. Il post del blog MSDN Working set! = Effettivo footprint di memoria riguarda la demistificazione del working set, la memoria di processo e come eseguire calcoli accurati sul consumo totale di RAM.

Non dirò che dovresti ignorare l'impronta di memoria della tua applicazione - ovviamente, più piccoli e più efficienti tendono ad essere desiderabili. Tuttavia, dovresti considerare quali sono le tue effettive esigenze.

Se stai scrivendo applicazioni client Windows Form e WPF standard destinate a essere eseguite sul PC di un individuo e che probabilmente sarà l'applicazione principale in cui opera l'utente, puoi farla franca essendo più apatico riguardo all'allocazione della memoria. (Fintanto che tutto viene deallocato.)

Tuttavia, per rivolgersi ad alcune persone qui che dicono di non preoccuparsene: se stai scrivendo un'applicazione Windows Forms che verrà eseguita in un ambiente di servizi terminal, su un server condiviso possibilmente utilizzato da 10, 20 o più utenti, allora sì , devi assolutamente considerare l'utilizzo della memoria. E dovrai essere vigile. Il modo migliore per risolvere questo problema è con una buona progettazione della struttura dei dati e seguendo le migliori pratiche relative a quando e cosa allocare.


45

Le applicazioni .NET avranno un'impronta maggiore rispetto alle applicazioni native, poiché entrambe devono caricare il runtime e l'applicazione nel processo. Se vuoi qualcosa di veramente ordinato, .NET potrebbe non essere l'opzione migliore.

Tuttavia, tieni presente che se la tua applicazione è per lo più inattiva, le pagine di memoria necessarie verranno sostituite dalla memoria e quindi non saranno un grosso peso per il sistema per la maggior parte del tempo.

Se vuoi mantenere un ingombro ridotto, dovrai pensare all'utilizzo della memoria. Ecco un paio di idee:

  • Riduci il numero di oggetti e assicurati di non trattenere alcuna istanza più a lungo del necessario.
  • Essere consapevoli di List<T>tipi simili che raddoppiano la capacità quando necessario in quanto possono portare a un aumento del 50% dei rifiuti.
  • Potresti considerare l'utilizzo di tipi di valore rispetto ai tipi di riferimento per forzare più memoria nello stack, ma tieni presente che lo spazio dello stack predefinito è solo 1 MB.
  • Evita gli oggetti di più di 85000 byte, poiché andranno a LOH che non è compresso e quindi potrebbe essere facilmente frammentato.

Probabilmente non è un elenco esaustivo, ma solo un paio di idee.


IOW, lo stesso tipo di tecniche che funzionano per ridurre la dimensione del codice nativo funzionano in .NET?
Robert Fraser

Immagino che ci sia qualche sovrapposizione, ma con il codice nativo hai più scelte quando si tratta di utilizzare la memoria.
Brian Rasmussen

17

Una cosa che devi considerare in questo caso è il costo della memoria del CLR. Il CLR viene caricato per ogni processo .Net e quindi tiene conto delle considerazioni sulla memoria. Per un programma così semplice / piccolo il costo del CLR dominerà la tua memoria.

Sarebbe molto più istruttivo costruire un'applicazione reale e visualizzare il costo di quella rispetto al costo di questo programma di base.


7

Nessun suggerimento specifico di per sé, ma potresti dare un'occhiata al CLR Profiler (download gratuito da Microsoft).
Dopo averlo installato, dai un'occhiata a questa pagina di istruzioni .

Dal how-to:

Questa procedura mostra come utilizzare lo strumento CLR Profiler per esaminare il profilo di allocazione della memoria dell'applicazione. È possibile utilizzare CLR Profiler per identificare il codice che causa problemi di memoria, ad esempio perdite di memoria e Garbage Collection eccessiva o inefficiente.


7

Potrei voler esaminare l'utilizzo della memoria di un'applicazione "reale".

Simile a Java, esiste una quantità fissa di overhead per il runtime indipendentemente dalle dimensioni del programma, ma il consumo di memoria sarà molto più ragionevole dopo quel punto.


4

Esistono ancora modi per ridurre il working set privato di questo semplice programma:

  1. NGEN la tua applicazione. Ciò rimuove i costi di compilazione JIT dal processo.

  2. Addestra la tua applicazione utilizzando MPGO riducendo l'utilizzo della memoria e poi NGEN.


2

Ci sono molti modi per ridurre il tuo impatto.

Una cosa con cui dovrai sempre convivere in .NET è che la dimensione dell'immagine nativa del tuo codice IL è enorme

E questo codice non può essere completamente condiviso tra le istanze dell'applicazione. Anche gli assemblaggi NGEN non sono completamente statici, hanno ancora alcune piccole parti che necessitano di JITting.

Le persone tendono anche a scrivere codice che blocca la memoria molto più a lungo del necessario.

Un esempio visto spesso: prendere un Datareader, caricare il contenuto in un DataTable solo per scriverlo in un file XML. Puoi facilmente imbatterti in un'eccezione OutOfMemoryException. OTOH, puoi usare un XmlTextWriter e scorrere il Datareader, emettendo XmlNodes mentre scorri il cursore del database. In questo modo, hai solo il record del database corrente e il suo output XML in memoria. Che non otterrà mai (o è improbabile) una maggiore generazione di Garbage Collection e quindi può essere riutilizzato.

Lo stesso vale per ottenere un elenco di alcune istanze, fare alcune cose (che genera migliaia di nuove istanze, che potrebbero rimanere referenziate da qualche parte), e anche se non ne hai bisogno in seguito, fai comunque riferimento a tutto fino a dopo il foreach. Annullare esplicitamente la tua lista di input e i tuoi sottoprodotti temporanei significa che questa memoria può essere riutilizzata anche prima di uscire dal ciclo.

C # ha un'eccellente funzionalità chiamata iteratori. Ti consentono di eseguire lo streaming di oggetti scorrendo l'input e mantengono l'istanza corrente solo fino a quando non ottieni quella successiva. Anche usando LINQ, non è ancora necessario tenerlo tutto intorno solo perché si desiderava che fosse filtrato.


1

Affrontare la domanda generale nel titolo e non la domanda specifica:

Se si utilizza un componente COM che restituisce molti dati (ad esempio grandi array 2xN di doppi) ed è necessaria solo una piccola frazione, è possibile scrivere un componente COM wrapper che nasconde la memoria da .NET e restituisce solo i dati che sono necessario.

Questo è quello che ho fatto nella mia applicazione principale e ha migliorato notevolmente il consumo di memoria.


0

Ho scoperto che l'utilizzo delle API SetProcessWorkingSetSize o EmptyWorkingSet per forzare periodicamente le pagine di memoria su disco in un processo di lunga esecuzione può comportare la scomparsa di tutta la memoria fisica disponibile su una macchina fino al riavvio della macchina. Avevamo una DLL .NET caricata in un processo nativo che avrebbe utilizzato l'API EmptyWorkingSet (un'alternativa all'utilizzo di SetProcessWorkingSetSize) per ridurre il working set dopo aver eseguito un'attività che richiede molta memoria. Ho scoperto che dopo un periodo compreso tra 1 giorno e una settimana una macchina mostrava il 99% di utilizzo della memoria fisica in Task Manager mentre nessun processo utilizzava un utilizzo significativo della memoria. Subito dopo la macchina smetterà di rispondere, richiedendo un riavvio forzato. Dette macchine erano oltre 2 dozzine di server Windows Server 2008 R2 e 2012 R2 in esecuzione su hardware sia fisico che virtuale.

Forse avere il codice .NET caricato in un processo nativo ha qualcosa a che fare con esso, ma usa EmptyWorkingSet (o SetProcessWorkingSetSize) a tuo rischio. Forse usalo solo una volta dopo il lancio iniziale della tua applicazione. Ho deciso di disabilitare il codice e lasciare che il Garbage Collector gestisca da solo l'utilizzo della memoria.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.