2010-09-29 8 views
6

Ich experimentiere mit einigen Multithreading-Konstruktionen, aber irgendwie scheint Multithreading nicht schneller als ein einzelner Thread zu sein. Ich habe es auf einen sehr einfachen Test mit einer verschachtelten Schleife (1000x1000) beschränkt, in der das System nur zählt.
Unten habe ich den Code sowohl für Single Threading und Multithreading und wie sie ausgeführt werden.
Das Ergebnis ist, dass der einzelne Thread die Schleife in ungefähr 110 ms schließt, während die zwei Threads auch ungefähr 112 ms dauern.
Ich glaube nicht, dass das Problem der Overhead des Multithreading ist. Wenn ich nur einen der beiden Runnables an den ThreadPoolExecutor übergebe, wird er in der Hälfte der Zeit des einzelnen Threads ausgeführt, was sinnvoll ist. Aber das Hinzufügen dieses zweiten Runnable macht es 10 mal langsamer. Beide 3,00 GHz-Kerne laufen zu 100%.
Ich denke, dass es PC-spezifisch sein kann, da der PC von jemand anderem Doppelgeschwindigkeitsergebnisse auf dem Multithreading zeigte. Aber was kann ich dagegen tun? Ich habe einen Intel Pentium 4 3.00GHz (2 CPUs) und Java jre6.

Prüfregeln:Multithreading nicht schneller als Single-Thread (einfacher Loop-Test)

// Single thread: 
long start = System.nanoTime(); // Start timer 
final int[] i = new int[1];  // This is to keep the test fair (see below) 
int i = 0; 
for(int x=0; x<10000; x++) 
{ 
    for(int y=0; y<10000; y++) 
    { 
     i++; // Just counting... 
    } 
} 
int i0[0] = i; 
long end = System.nanoTime(); // Stop timer 

Dieser Code in etwa 110 ms ausgeführt wird.

// Two threads: 

start = System.nanoTime(); // Start timer 

// Two of the same kind of variables to count with as in the single thread. 
final int[] i1 = new int [1]; 
final int[] i2 = new int [1]; 

// First partial task (0-5000) 
Thread t1 = new Thread() { 
    @Override 
    public void run() 
    { 
     int i = 0; 
     for(int x=0; x<5000; x++) 
      for(int y=0; y<10000; y++) 
       i++; 
     i1[0] = i; 
    } 
}; 

// Second partial task (5000-10000) 
Thread t2 = new Thread() { 
    @Override 
    public void run() 
    { 
     int i = 0; 
     for(int x=5000; x<10000; x++) 
      for(int y=0; y<10000; y++) 
       i++; 
     int i2[0] = i; 
    } 
}; 

// Start threads 
t1.start(); 
t2.start(); 

// Wait for completion 
try{ 
    t1.join(); 
    t2.join(); 
}catch(Exception e){ 
    e.printStackTrace(); 
} 

end = System.nanoTime(); // Stop timer 

Dieser Code wird in etwa112 ms ausgeführt.

Edit: Ich änderte die Runnables zu Threads und löste den ExecutorService (zur Vereinfachung des Problems).

Edit: versucht, einige Vorschläge

+0

Also, hast du die Vorschläge ausprobiert? –

+0

Ah, Pentium4 - siehe meine aktualisierte Antwort :) – snemarch

Antwort

11

Sie wollen auf jeden Fall nicht weiter polling Thread.isAlive() - das brennt eine Menge CPU-Zyklen ohne triftigen Grund. Verwenden Sie stattdessen Thread.join().

Auch ist es wahrscheinlich keine gute Idee, wenn die Threads die Ergebnis-Arrays direkt, Cache-Zeilen und alle erhöhen. Aktualisieren Sie lokale Variablen und führen Sie einen einzelnen Speicher aus, wenn die Berechnungen abgeschlossen sind.

EDIT:

Völlig übersehen, dass Sie einen Pentium 4 verwenden Soweit ich weiß, gibt es keine Multi-Core-Versionen des P4 - die Illusion von Multi-Core zu geben, es Hyper-Threading hat: zwei logische Kerne teilen die Ausführungseinheiten von einem physikalischen Kern. Wenn Ihre Threads von denselben Ausführungseinheiten abhängig sind, entspricht Ihre Leistung der Leistung eines einzelnen Threads (oder schlechter als!). Sie benötigen zum Beispiel Gleitkommaberechnungen in einem Thread und ganzzahlige Berechnungen in einem anderen Thread, um Leistungsverbesserungen zu erzielen.

Die P4 HT-Implementierung wurde viel kritisiert, neuere Implementierungen (core2) sollten besser sein.

+0

+1 - Der erste Absatz ist wahrscheinlich der größte Teil des Unterschieds. –

+0

+1 - Eigentlich beide Vorschläge beschleunigen den Prozess erheblich, danke. Aber es gibt etwas Seltsames: Die Verwendung von Thread.isAlive() in Kombination mit direkt inkrementierenden Arrays ist schneller (800 ms) als die Verwendung von Thread.join() (2200 ms), aber die Verwendung von isAlive() in Kombination mit Ihrem zweiten Vorschlag ist langsamer (190 ms) als beitreten() (114 ms). Wie auch immer, die Verwendung beider Vorschläge beschleunigt das System von 2200 ms auf 114: D. Ihr zweiter Vorschlag beschleunigt den einzelnen Thread jedoch auf etwa 110 ms, so dass es jetzt noch keinen Unterschied gibt. – RemiX

+0

Ein Unterschied von weniger als 10ms sagt Ihnen nichts wirklich, wenn Sie auf einem Multitasking-Betriebssystem laufen - Sie müssen die Iterationen erhöhen, um den Geschwindigkeitsunterschied zuverlässiger zu messen :) – snemarch

1

Sie tun nichts mit i, so dass Ihre Schleife wahrscheinlich weg gerade optimiert ist.

+0

Eigentlich habe ich den Wert von i am unteren Rand gedruckt (aber es ist nicht im Code angezeigt). – RemiX

+0

Die Zeiten sind konsistent mit der Optimierung, aber nicht optimiert weg. Ich möchte, dass der Test wiederholt wird (ohne den Prozess neu zu starten). Ein Problem, das Threads in diesem Zusammenhang haben können, besteht darin, dass HotSpot in einem anderen Thread ausgeführt wird und der zusätzliche Thread möglicherweise den nicht optimierten Code für einige Zeit ausführt. –

+0

Ein anderer Thread, der genauso wie t2 (nur dann 10000x10000) arbeitet, ist in 107 ms abgeschlossen (schneller als t1 und t2 zusammen), oder meintest du das nicht? – RemiX

2

Ich bin überhaupt nicht überrascht über den Unterschied. Sie verwenden das Parallelitätsframework von Java, um Ihre Threads zu erstellen (obwohl ich keine Garantie dafür sehe, dass zwei Threads überhaupt erstellt werden, da der erste Job möglicherweise abgeschlossen wird, bevor der zweite ausgeführt wird.

Es gibt wahrscheinlich alle Arten von Sperren und Synchronisation hinter den Kulissen, die Sie tatsächlich für Ihren einfachen Test nicht brauchen. kurz gesagt ich denke, das Problem der Aufwand für Multithreading ist.

+0

Ich habe es auch mit nur zwei Threads getestet und thread1.start() verwendet, das gleiche Ergebnis zeigend. Außerdem funktioniert ein Runnable im ExecutorService sehr schnell und schließlich funktioniert ein anderer Computer gut mit diesem Code. – RemiX

4

Versuchen Sie, die Größe des Arrays zu erhöhen etwas. Nein, wirklich.

Kleine Objekte, die nacheinander im selben Thread zugewiesen werden, werden in der Regel anfänglich sequenziell zugewiesen in der gleichen Cache-Zeile. Wenn Sie zwei Kerne auf die gleiche Cache-Zeile zugreifen (und dann macht micro-benhcmark im Wesentlichen nur eine Sequenz von Schreibvorgängen an die gleiche Adresse), dann müssen sie für den Zugriff kämpfen.

Es gibt eine Klasse in java.util.concurrent, die eine Reihe von unbenutzten long Feldern enthält. Ihr Zweck besteht darin, Objekte, die häufig von verschiedenen Threads verwendet werden, in verschiedene Cache-Zeilen zu trennen.

+0

Ich benutze für jeden Thread ein anderes Array, also glaube ich nicht, dass sie um Zugang kämpfen müssen ... oder habe ich das falsch verstanden? – RemiX

+4

@RemiX: Sie sind beide auf dem Heap zugeordnet, i2 wird direkt nach i1 zugewiesen. Es besteht eine ziemlich hohe Wahrscheinlichkeit, dass sie in derselben Cache-Line landen. – snemarch

+0

+1 - 2200 ms bis 280 ms, indem Sie einfach die Größe der Arrays auf 10 erhöhen. Leider ist der Effekt mit Ihren anderen Vorschlägen nicht mehr so ​​gut. Gut zu erinnern, obwohl. – RemiX

1

Haben Sie die Anzahl der verfügbaren Kerne auf Ihrem PC mit Runtime.getRuntime() überprüft? AvailableProcessors()?

+0

Habe gerade, und es sagt 2 Prozessoren. Außerdem kann ich sehen, dass sie im Task-Manager arbeiten. – RemiX

0

Ihr Code erhöht einfach eine Variable - das ist sowieso eine sehr schnelle Operation. Sie profitieren nicht viel von der Verwendung von mehreren Threads hier. Leistungsgewinne sind ausgeprägter, wenn Thread-1 auf eine externe Antwort warten muss oder komplexere Berechnungen durchführen muss, währenddessen Ihr Haupt-Thread oder ein anderer Thread die Verarbeitung fortsetzen kann und nicht warten muss. Sie scheinen mehr Gewinne zu haben, wenn Sie höher gezählt haben oder mehr Threads verwendet haben (wahrscheinlich ist eine sichere Zahl die Anzahl der CPU/Kerne in Ihrem Rechner).