2015-11-21 29 views
5

Stavo imparando di più su threading, e ho creato un piuttosto semplice applicazione WPF con il seguente codice (x64 piattaforma di costruzione)c utilizzo della memoria # filo

public partial class MainWindow : Window 
{ 
    public MainWindow() 
    { 
     InitializeComponent(); 

     for (var i = 0; i <= 20000; i++) 
     { 
      Thread thread = new Thread(Test); 
      thread.IsBackground = true; 

      thread.Start(); 
     } 
    } 

    public void Test() 
    { 
     Thread.Sleep(20000); 
    } 
} 

Quando ho eseguito questo codice, procedura richiede circa 560MB di RAM mentre tutti i thread stanno funzionando/dormendo.

Highest peak

su di essa è completato, l'utilizzo di processo è giù per approssimativamente 125 MB di RAM.

La mia domanda è, perché il processo utilizza 125 MB di RAM a quel punto, quando l'applicazione stessa (senza esempio di thread) utilizza solo 30 MB di RAM?

Mantiene alcuni dei thread attivi per eventuale ri/utilizzo o qualcos'altro sta succedendo?

EDIT:

A causa di alcuni suggerimenti su come migliorare questo codice, vorrei sottolineare che non sto chiedendo un modo per migliorare, ma per individuare il motivo di questo comportamento.

EDIT 2:

Questa non è una discussione correlata, ma ho provato un caso con un ampio elenco string in memoria, e non produce gli stessi risultati. Quando l'elenco era completamente caricato in memoria, ci sono voluti circa 1,3 GB di memoria, ma dopo che l'elenco è stato impostato su NULL e GC.Collect() è stato chiamato, l'utilizzo della memoria è sceso a 30 MB come previsto.

Codice:

public partial class MainWindow : Window 
{ 
    List<string> stringArray = new List<string>(); 

    public MainWindow() 
    { 
     InitializeComponent(); 


     for (var i = 0; i <= 100000000; i++) 
     { 
      //Thread thread = new Thread(Test); 
      //thread.IsBackground = false; 

      //thread.Start(); 

      stringArray.Add("Some very long string to pump up the memory volume 2 reloaded"); 
     } 

     stringArray = null; 
     GC.Collect(); 
    } 



} 

Lowest peak

+1

Quando un thread termina l'oggetto thread diventa idoneo per la raccolta dei rifiuti ad un certo punto imprecisato in futuro. –

+5

Bene per i principianti, a ciascun thread viene assegnato uno stack da 1 MB predefinito, quindi è necessario aggiungere circa 200 MB. – OldProgrammer

+0

@AlexK. , ho provato ad aggiungere 'GC.Collect();' ad ogni completamento del thread, ma sono ancora bloccato a ~ 125 MB di ram, anche se lascio l'app per funzionare per diversi minuti. Non scende mai a ~ 30 MB. – Robert

risposta

7

Parte della memoria utilizzata da ogni thread viene rilasciata quando il thread viene completato. Ecco perché il consumo di memoria scende da 560 MB a 125 MB quando tutti i thread in background sono completi.

Il resto della memoria viene allocato sull'heap gestito e verrà liberato dal garbage collector.

Quando l'applicazione è inattiva, non viene allocata alcuna nuova memoria, quindi non è necessario eseguire il Garbage Collector. Hai menzionato in un commento che forzare GC utilizzando GC.Collect() non ha aiutato, ma tieni presente che Thread ha implementato un finalizzatore, quindi sopravviverà alla prima raccolta.È necessario utilizzare

GC.Collect(); 
GC.WaitForPendingFinalizers(); 
GC.Collect(); 

per assicurarsi che la memoria venga liberata.

Questo dovrebbe ridurre l'utilizzo della memoria al livello prima di iniziare i thread.

+0

funziona come un fascino. Grazie – Robert

0

il meccanismo di filettatura è parte del problema. Oltre a occupare così tanta CPU durante l'esecuzione (riducendo l'opportunità di raccolta dei dati inutili), l'utilizzo della memoria è a portata di mano.

Qualcosa come questo (meno le informazioni di debug) manterrà il consumo di memoria abbastanza uniforme. E la tua CPU dovrebbe essere 'loafing' durante l'operazione (la parola chiave statica proviene dal mio test in un'applicazione console). .Net framework 4.0 o superiore è richiesto. Se sei vincolato a versioni perverse di .net allora suggerirei una leggera pausa prima di iniziare la nuova attività per consentire alla garbage collection di fare la sua magia.

private static void ThreadStuff() 
{ 
    long startSet = System.Diagnostics.Process.GetCurrentProcess().WorkingSet64; 
    List<long> endSet = new List<long>(); 
    for (var i = 0; i <= 20000; i++) 
    { 
     Action Act = new Action(() => Test(i)); 

     Task Tsk = new Task(Act); 
     Tsk.Start(); 
     endSet.Add(System.Diagnostics.Process.GetCurrentProcess().WorkingSet64); 
     int worker; 
     int ioCompletion; 
     ThreadPool.GetMaxThreads(out worker, out ioCompletion); 
     Console.WriteLine(worker); 
     Console.WriteLine(ioCompletion); 
    } 
    Console.WriteLine(startSet.ToString("###,###,###,###,###,###.####")); 
    Console.WriteLine(endSet.Average().ToString("###,###,###,###,###,###.####")); 
} 
public static void Test(int Index) 
{ 
    Thread.Sleep(2000); 
    Console.WriteLine(Index.ToString() + " Done"); 
} 
+0

vedo che stai usando le attività, ma cosa succede se cambio il mio codice per essere un thread in primo piano? Oltre a ciò, non sto cercando un modo per ottimizzare il mio codice, ma un motivo per cui l'utilizzo della memoria non scende mai sotto i 100 MB, anche se tutti i thread sono stati completati alcuni minuti fa. – Robert

+1

Questa è una soluzione a un problema che non sta chiedendo di risolvere. Vuole solo sapere * perché * questo accade. – usr

+0

@usr: 'Perché' sta accadendo a causa del meccanismo che sta invocando i thread L'uso di un meccanismo diverso fa andare via il problema. – Paul

2

Si sta eseguendo un'applicazione a 64 bit che crea solo molti thread. Innanzitutto, è importante sapere che l'esecuzione di una build Debug di un'applicazione può produrre risultati diversi rispetto alla creazione di una versione di Release poiché le variabili vengono conservate artificialmente più a lungo del necessario in memoria. Quindi non saranno raccolti il ​​prima possibile.

il garbage collector è altamente ottimizzato e verrà attivato quando:

  • Il Gen0 è stato completamente assegnato (la dimensione del Gen0 si basa su euristica, come la velocità con cui l'applicazione fare assegnazioni, ecc);
  • La memoria disponibile si sta esaurendo;
  • GC.Collect viene chiamato.

Il motivo per cui il GC non raccoglie la memoria così velocemente come si pensava è perché non ci sono semplicemente motivi per cui il GC raccoglie memoria. Se la tua applicazione fosse in esecuzione su una piattaforma a 32 bit, avrebbe ancora 1,5 GB di memoria disponibile. Nel tuo caso, stai utilizzando un'applicazione a 64 bit: c'è molta memoria disponibile. Tuttavia, se si stesse eseguendo un'applicazione "reale" con molte più allocazioni di memoria, si avrebbe un risultato diverso e probabilmente molte più raccolte (e quindi un set di lavoro più piccolo).

Infine, chiamare GC.Collect è spesso inutile e può compromettere l'euristica del GC che può influire negativamente sulle prestazioni dell'applicazione perché GC.Collect attiverà la raccolta di tutte le generazioni.

https://msdn.microsoft.com/en-us/library/xe0c2357(v=vs.110).aspx