2012-04-03 10 views
8

Spero che questo sia un post valido qui, è una combinazione di problemi C# e hardware.C# prestazioni che variano a causa della memoria

Sto analizzando il nostro server perché abbiamo riscontrato problemi con le prestazioni della nostra libreria quant (scritta in C#). Ho simulato gli stessi problemi di prestazioni con un semplice codice C#, che comporta un utilizzo molto intenso della memoria.

Il codice di seguito è in una funzione generata da un threadpool, fino a un massimo di 32 thread (poiché il nostro server ha 4x CPU x 8 core ciascuno).

Questo è tutto su Net 3.5

Il problema è che stiamo ottenendo selvaggiamente differenti prestazioni. Eseguo la funzione qui sotto 1000 volte. Il tempo medio impiegato per il codice da eseguire potrebbe essere, ad esempio, 3.5s, ma il più veloce sarà solo 1.2s e il più lento sarà 7s- per la stessa identica funzione!

ho graficamente l'utilizzo della memoria contro i tempi e ci doesnt sembrano essere alcuna correlazione con il GC calci in.

Una cosa che ho notato è che durante l'esecuzione in un singolo thread i tempi sono identici e non vi non è una deviazione selvaggia. Ho anche testato algoritmi legati alla CPU e anche i tempi sono identici. Questo ci ha portato a chiederci se il bus di memoria non può farcela.

Mi chiedevo potrebbe essere un altro problema .net o C#, o è qualcosa correlato al nostro hardware? Questa sarebbe la stessa esperienza se avessi usato C++ o Java ?? Stiamo usando 4x Intel x7550 con 32 GB di RAM. C'è un modo per aggirare questo problema in generale?

Stopwatch watch = new Stopwatch(); 
watch.Start(); 
List<byte> list1 = new List<byte>(); 
List<byte> list2 = new List<byte>(); 
List<byte> list3 = new List<byte>(); 


int Size1 = 10000000; 
int Size2 = 2 * Size1; 
int Size3 = Size1; 

for (int i = 0; i < Size1; i++) 
{ 
    list1.Add(57); 
} 

for (int i = 0; i < Size2; i = i + 2) 
{ 
    list2.Add(56); 
} 

for (int i = 0; i < Size3; i++) 
{ 
    byte temp = list1.ElementAt(i); 
    byte temp2 = list2.ElementAt(i); 
    list3.Add(temp); 
    list2[i] = temp; 
    list1[i] = temp2; 
} 
watch.Stop(); 

(il codice è solo pensata per sottolineare la memoria)

vorrei includere il codice di thread, ma abbiamo usato una libreria di thread non standard.

EDIT: Ho ridotto "size1" a 100000, che in pratica non utilizza molta memoria e ho ancora un sacco di jitter. Questo suggerisce che non è la quantità di memoria che viene trasferita, ma la frequenza delle acquisizioni di memoria?

+0

Sono in corso altri processi durante il benchmark? Anche il sistema operativo ha bisogno del tempo della CPU. Se stai utilizzando tutti i core virtuali durante il tuo benchmark, sei virtualmente (perdonatemi il gioco di parole) garantito che i processi non correlati richiederanno del tempo di CPU durante il test. –

+5

Non abbiamo abbastanza informazioni per fare altro che speculare. Detto questo, i miei soldi sono sulla tua "libreria threadpool non standard" che non assegna abbastanza thread per eseguirlo in parallelo. Se si eseguono 50 copie e si assegnano solo 20 thread (ad esempio), 10 iterazioni dovranno attendere (in media) altre 2 iterazioni da completare affinché un thread si liberi. Questo potrebbe spiegare le deviazioni che stai vedendo. –

+8

Solo un'idea: dal momento che sembra conoscere la dimensione della lista, dovresti passarla al costruttore (o usare semplicemente gli array). Quindi si evitano le ridistribuzioni se gli array sottostanti. –

risposta

0

L'elenco utilizza gli array internamente per l'archiviazione. Credo che tenterà di raddoppiare la dimensione della matrice ogni volta che raggiunge il limite di spazio libero nella lista.

Come si entra nel ciclo, ha bisogno di blocchi sempre più grandi di memoria contigua per allocare i nuovi array man mano che l'elenco cresce. Con un thread, questo è abbastanza facile. Con 2+ thread, sei in competizione per grossi pezzi di memoria contigua. Avrebbe attivato il GC in momenti casuali mentre gli array diventavano più grandi e la memoria contigua era più difficile da trovare.

+0

Ciao, ho cambiato il Liste per byte di dimensioni predeterminate [], dove la dimensione è 10.000.000 e i tempi per la funzione da completare sono ancora completamente casuale. Il più veloce è 462 ms, la media è 1192 ms e il più lento è 2509 ms, più del doppio della media. – mezamorphic

1

Qui si stanno raggiungendo limiti di macchina piuttosto fondamentali. Hai un sacco di core ma c'è ancora solo un bus di memoria. Quindi, se i tuoi thread fanno un sacco di mescolanze di dati, è probabile che vengano limitati dalla larghezza di banda di quel singolo bus. Questa è la legge di Amdahl al lavoro.

C'è una possibilità di ottimizzazione, dipende dal tipo di sistema operativo in esecuzione. Questo è il tipo di hardware del server, ma se si ha una versione non server di Windows, il garbage collector verrà eseguito in modalità workstation. Puoi quindi utilizzare l'elemento <gcServer> nel file .config dell'app per chiedere la versione server del raccoglitore. Utilizza più heap in modo che i thread non combatteranno per il blocco dell'heap GC tanto spesso quando allocano memoria. YMMV.

0

Assicurarsi che la configurazione di runtime ha gcserver = true

+0

Indagato che ha reso la media del processo più veloce ma non ha ridotto la variazione nei tempi. – mezamorphic

+0

Sarei interessato a vedere i risultati dell'utilizzo di parallel.for nel codice per vedere l'impatto delle chiamate asincrone rende – Shay

4

Non c'è abbastanza per andare avanti, ma qui ci sono alcune aree per iniziare la ricerca:

  • La variabilità è il risultato di Stato GC interna . Il GC gestisce dinamicamente le dimensioni dei vari pool. Se inizi con dimensioni di pool diverse, otterrai un comportamento GC diverso durante le esecuzioni.
  • Modelli moiré nella pianificazione dei thread. A seconda delle variazioni casuali nella sequenza dei thread, potresti avere schemi di contesa più o meno favorevoli. Se c'è qualche periodicità, ciò può portare ad un effetto amplificato simile a un'interferenza costruttiva.
  • Condivisione errata. Se si hanno due thread che colpiscono entrambi gli indirizzi di memoria abbastanza vicini da essere collocati nella cache del processore, si noterà una marcata diminuzione delle prestazioni poiché i processori devono impiegare molto tempo a risincronizzare le loro cache. A seconda della modalità di organizzazione dei dati e dell'allocazione dei thread per elaborarli, è possibile ottenere modelli in condivisione falsa basati su variazioni all'inizio.
  • Un altro processo nel sistema sta occupando il tempo del processore. Potrebbe essere utile utilizzare una misura della durata della modalità utente del processo anziché quella del muro. (C'è un accessorio per questo nella classe Process da qualche parte).
  • La macchina è in esecuzione vicino al limite di memoria fisica completo. Lo scambio su disco si verifica con uno schema casuale più o meno.
+1

# 3 viene comunemente chiamato [false sharing] (http://en.wikipedia.org/wiki/False_sharing). –

+0

@RonWarholic Grazie. Sapevo che c'era un termine per questo, proprio non riuscivo a ricordare. –

0

A questo punto sembra che indovinare qualsiasi cosa sarebbe semplicemente una congettura. Davvero quello che ti serve sono più informazioni.

vorrei collegare un profiler o setup alcuni contatori delle prestazioni di Windows:

http://support.microsoft.com/kb/300504

si dovrebbe essere in grado di aggiungere alcuni contatori delle prestazioni centrate sul processo. Puoi vedere quanti fili vengono centrifugati, l'utilizzo della memoria, ecc. Prenderò alcuni degli altri suggerimenti qui e misurerò lo scenario che stai cercando. Se si esegue il dump dei dati del contatore delle prestazioni in un file csv, è anche possibile rappresentare graficamente i risultati abbastanza rapidamente per ottenere alcuni dati buoni da masticare effettivamente. Se riesci a scoprire quale metrica sta cambiando con lo scenario 1.2s vs 7s, puoi iniziare a formulare ipotesi su cosa sta succedendo e continuare a perfezionarla.

0

Chiamate sincrone a risorse condivise, come la console o il file Il sistema, ridurrà in modo significativo le prestazioni, ma dal punto di vista delle cose, questo codice è solo al massimo della CPU e gli scostamenti di tempo devono essere dovuti ad altri processi che richiedono il tempo della CPU.

Problemi correlati