2011-12-26 21 views
7

Il seguente test nunit confronta prestazioni tra l'esecuzione di un singolo filo rispetto esecuzione 2 fili su una macchina dual core. In particolare, si tratta di una macchina virtuale Windows 7 dual core VMWare su un host SLED Linux quad core con un Dell Inspiron 503.Prestazioni dual-core peggiori rispetto al single core?

Ogni thread scorre semplicemente e incrementa 2 contatori, addCounter e readCounter. Questo test è stato inizialmente testato su un'implementazione di Queue, che è stata scoperta peggiorare su una macchina multi-core. Quindi nel restringere il problema al piccolo codice riproducibile, hai qui nessuna coda solo incrementando le variabili e per scioccare e sgomentare, è molto più lento con 2 thread e uno.

Quando si esegue il primo test, il Task Manager mostra 1 dei core occupati al 100% con l'altro core quasi inattivo. Ecco l'output di test per il test a singolo thread:

readCounter 360687000 
readCounter2 0 
total readCounter 360687000 
addCounter 360687000 
addCounter2 0 

Si vedono oltre 360 ​​milioni di incrementi!

Successivamente il test a doppio filo mostra il 100% occupato su entrambi i core per l'intera durata di 5 secondi del test. Tuttavia, viene mostrato solo l'output:

readCounter 88687000 
readCounter2 134606500 
totoal readCounter 223293500 
addCounter 88687000 
addCounter2 67303250 
addFailure0 

Questo è solo 223 milioni di incrementi di lettura. Qual è la creazione di Dio sono quei 2 CPU che fanno per quei 5 secondi per ottenere meno lavoro svolto?

Eventuali indizi? E puoi eseguire i test sulla tua macchina per vedere se ottieni risultati diversi? Un'idea è che forse la prestazione dual core VMWare non è ciò che si spera.

Modifica: ho provato quanto sopra su un laptop quad core senza vmware e ho ottenuto prestazioni degradate simili. Così ho scritto un altro test simile al precedente ma che ha ciascun metodo di thread in una classe separata. Il mio scopo era quello di testare 4 core.

Bene, questo test ha mostrato risultati eccellenti che sono migliorati quasi linearmente con 1, 2, 3 o 4 core.

Con alcuni esperimenti ora su entrambe le macchine, sembra che le prestazioni appropriate avvengano solo se i metodi di thread principale sono su istanze diverse invece che sulla stessa istanza.

In altre parole, se sulla stessa istanza di una particolare classe si utilizza il metodo di immissione principale di più thread, le prestazioni su un multi-core peggioreranno per ogni thread che si aggiunge, anziché come meglio si potrebbe assumere.

Sembra quasi che il CLR si "sincronizzi" in modo che solo un thread alla volta possa essere eseguito su tale metodo. Tuttavia, i miei test dicono che non è il caso. Quindi non è ancora chiaro cosa sta succedendo.

Ma il mio problema sembra essere risolto semplicemente rendendo istanze separate di metodi per eseguire thread come punto di partenza.

saluti, Wayne

EDIT:

Ecco un test di unità aggiornato che test 1, 2, 3, 4 & filettature con tutti sulla stessa istanza di una classe. L'utilizzo di matrici con variabili utilizza nel ciclo del filo almeno 10 elementi separati. E le prestazioni si degradano ancora significativamente per ogni thread aggiunto.

using System; 
using System.Threading; 
using NUnit.Framework; 

namespace TickZoom.Utilities.TickZoom.Utilities 
{ 
    [TestFixture] 
    public class MultiCoreSameClassTest 
    { 
     private ThreadTester threadTester; 
     public class ThreadTester 
     { 
      private Thread[] speedThread = new Thread[400]; 
      private long[] addCounter = new long[400]; 
      private long[] readCounter = new long[400]; 
      private bool[] stopThread = new bool[400]; 
      internal Exception threadException; 
      private int count; 

      public ThreadTester(int count) 
      { 
       for(var i=0; i<speedThread.Length; i+=10) 
       { 
        speedThread[i] = new Thread(SpeedTestLoop); 
       } 
       this.count = count; 
      } 

      public void Run() 
      { 
       for (var i = 0; i < count*10; i+=10) 
       { 
        speedThread[i].Start(i); 
       } 
      } 

      public void Stop() 
      { 
       for (var i = 0; i < stopThread.Length; i+=10) 
       { 
        stopThread[i] = true; 
       } 
       for (var i = 0; i < count * 10; i += 10) 
       { 
        speedThread[i].Join(); 
       } 
       if (threadException != null) 
       { 
        throw new Exception("Thread failed: ", threadException); 
       } 
      } 

      public void Output() 
      { 
       var readSum = 0L; 
       var addSum = 0L; 
       for (var i = 0; i < count; i++) 
       { 
        readSum += readCounter[i]; 
        addSum += addCounter[i]; 
       } 
       Console.Out.WriteLine("Thread readCounter " + readSum + ", addCounter " + addSum); 
      } 

      private void SpeedTestLoop(object indexarg) 
      { 
       var index = (int) indexarg; 
       try 
       { 
        while (!stopThread[index*10]) 
        { 
         for (var i = 0; i < 500; i++) 
         { 
          ++addCounter[index*10]; 
         } 
         for (var i = 0; i < 500; i++) 
         { 
          ++readCounter[index*10]; 
         } 
        } 
       } 
       catch (Exception ex) 
       { 
        threadException = ex; 
       } 
      } 
     } 

     [SetUp] 
     public void Setup() 
     { 
     } 


     [Test] 
     public void SingleCoreTest() 
     { 
      TestCores(1); 
     } 

     [Test] 
     public void DualCoreTest() 
     { 
      TestCores(2); 
     } 

     [Test] 
     public void TriCoreTest() 
     { 
      TestCores(3); 
     } 

     [Test] 
     public void QuadCoreTest() 
     { 
      TestCores(4); 
     } 

     public void TestCores(int numCores) 
     { 
      threadTester = new ThreadTester(numCores); 
      threadTester.Run(); 
      Thread.Sleep(5000); 
      threadTester.Stop(); 
      threadTester.Output(); 
     } 
    } 
} 
+0

Si sta eseguendo questo in modalità di rilascio senza il debugger allegato? –

+0

Nota: il codice non ha operazioni di sincronizzazione thread (blocchi o interbloccati o altro). Se la si mantiene così, si può anche usare Random per tutti i valori poiché non c'è modo di avere il codice multithread in esecuzione corretta senza sincronizzazione. –

+0

Jim, ci ho provato in entrambi i modi. Ma i numeri discussi sono durante la modalità Debug. Alexel ... sono consapevole della necessità di serrature e interfogliati. Ovviamente. Ma questo è solo un codice sperimentale. L'esperimento è iniziato con le serrature ma le prestazioni erano orribili, peggio con più thread/core. Così ho rimosso i lucchetti per vedere se è più veloce. No ... ancora male. Quindi sto cercando di isolare perché 4 core eseguono lo stesso codice senza alcun blocco quindi cane lento ??? Sai perché? – Wayne

risposta

5

Questo è solo 223 milioni di incrementi di lettura. Qual è la creazione di Dio sono quei 2 CPU che fanno per quei 5 secondi per ottenere meno lavoro svolto?

probabilmente stai correndo nella cache contesa - quando una singola CPU incrementando il vostro intero, può farlo nella propria cache L1, ma appena due CPU iniziare "lotta" sopra lo stesso valore, la linea della cache su cui è attiva deve essere copiata avanti e indietro tra le loro cache ogni volta che ciascuna accede a essa. Il tempo extra impiegato per copiare i dati tra le cache aggiunge veloce, soprattutto quando l'operazione che stai eseguendo (incrementando un intero) è così banale.

+0

Angolo interessante. Si prega di vedere le mie modifiche dai risultati di più esperimenti. Pensi che la contesa della cache L1 avvenga nel CLR quando il punto di ingresso di un thread si trova nelle stesse istanze della classe rispetto a istanze separate? Se è così, potresti averlo sulla spiegazione. – Wayne

+0

Per riferimento, questo problema di "scambio di memoria" è chiamato "thrashing" - http://en.wikipedia.org/wiki/Thrashing_(computer_science) –

+0

Altri test dimostrano che con l'aggiunta dell'uso di Interlock per l'incremento, quindi le prestazioni in modo coerente : Diminuisce da 1 solo a 2 thread su un quad core, quindi va su UP sul terzo thread e diminuisce nuovamente con l'aggiunta di un quarto thread. È chiaro che c'è qualcosa da fare con "multi-CPU" che mi manca. C'è una sorta di "interazione" in corso, che si tratti di cache L1 qualcos'altro. – Wayne

0

Un paio di cose:

  1. Probabilmente si dovrebbe testare ogni impostazione almeno 10 volte e prendere la media
  2. Per quanto ne so, non è esatto Thread.sleep - dipende come il sistema operativo cambia i thread
  3. Thread.join non è immediato. Di nuovo, dipende da come il sistema operativo cambia i thread

Un modo migliore per testare sarebbe eseguire un'operazione computazionale intensiva (diciamo, somma da uno a un milione) su due configurazioni e cronometrare. Per esempio:

  1. Tempo quanto tempo ci vuole sommare da uno ad un tempo milioni
  2. quanto tempo ci vuole per una somma di 500000 su un filo e un 500.001-1.000.000 su un altro

Avevi ragione quando pensavi che due thread avrebbero funzionato più velocemente di un thread. Ma i tuoi non sono gli unici thread in esecuzione: il sistema operativo ha thread, il tuo browser ha thread e così via. Tieni presente che i tuoi orari non saranno esatti e potrebbero persino fluttuare.

Infine, ci sono other reasons (vedere la diapositiva 24) perché i thread funzionano più lentamente.

+0

Tutti i fattori che hai menzionato sono stati verificati. Questo è nient'altro che era in esecuzione sulla macchina, sul browser, ecc. Solo questo processo stava richiedendo a CPU excpet un sorrisone di Task Manager per guardarlo. Inoltre, ho eseguito questo test ripetutamente e ho scoperto che (se modificato) se i thread si avviano nella stessa istanza di una classe, le prestazioni si degradano in modo costante per thread aggiunto. Ma su istanze separate, le prestazioni migliorano in modo significativo e in modo quasi lineare per ogni thread aggiunto.Alcuna spiegazione? – Wayne

+0

In tal caso, sono d'accordo con duskwuff. È più probabile che il thrashing si verifichi quando i thread si trovano nella stessa istanza. Prova questo: invece di aggiungere in readCounter e readCounter2, aggiungi a un array ad es. arr [0] e arr [5]. Guarda cosa succede quando cambi la spaziatura tra i due. –

+0

Quindi, usando un array per forzarli in linee di cache separate? Interessante. Userò lo stesso codice di test sopra e cercherò esattamente quello. Dammi qualche minuto, per favore. Non vedo l'ora di provarlo. – Wayne

Problemi correlati