2015-11-01 14 views
7

Ich untersuche die beste Leistung von Multithreading-Inkrement. Ich überprüfte Implementierung auf der Grundlage von Synchronisierung, AtomicInteger und benutzerdefinierte Implementierung wie in AtomicInteger, aber mit ParkNanos (1) auf fehlgeschlagenem CAS.Java Increment Benchmark

private int customAtomic() { 
     int ret; 
     for (;;) { 
      ret = intValue; 
      if (unsafe.compareAndSwapInt(this, offsetIntValue, ret, ++ret)) { 
       break; 
      } 
      LockSupport.parkNanos(1); 
     } 
     return ret; 
    } 

I Benchmark gemacht basierend auf JMH: klare Ausführung jedes Verfahren, jede Methode mit verbrauchen CPU (1,2,4,8,16 mal) und nur CPU verbrauchen. Jede Benchmark-Methode auf Intel (R) Xeon (R) CPU E5-1680 v2 @ 3.00 GHz, 8 Core + 8 HT 64 GB RAM, auf 1-17 Threads durchgeführt. Die Ergebnisse haben mich überrascht:

  1. CAS am effektivsten in 1 Thread. 2-Faden - ähnliches Ergebnis mit Monitor. 3 und mehr - schlechter als Monitor, ~ 2 mal.
  2. Die benutzerdefinierte Implementierung ist in den meisten Fällen in 2-3 mal besser, als Monitor.
  3. Aber in der benutzerdefinierten Implementierung geschieht gelegentlich zufällig schlechte Ausführung. Guter Fall - 50 OP/Mikrosekunden, schlechter Fall - 0,5 OP/Mikrosekunden.

Fragen:

  1. Warum Atomicinteger nicht auf Synchronize basiert, ist es produktiver, dann aktuelle impl?
  2. Warum AtomicInteger LockSupport.parkNanos (1) nicht verwendet, auf CAS fehlschlagen?
  3. Warum passiert diese Spikes in benutzerdefinierten Implementierung?

CustomIncrementGraph

Ich habe versucht, diesen Test einige Male durchzuführen, und Spike geschieht immer in unterschiedlicher Anzahl Threads. Auch ich habe diesen Test in einer anderen Maschine versucht, das Ergebnis ist das gleiche. Vielleicht ist es ein Testproblem. In "bad case" von benutzerdefinierten impl in StackProfiler, ich sehe:

....[Thread state distributions].................................................................... 
50.0%   RUNNABLE 
49.9%   TIMED_WAITING 

....[Thread state: RUNNABLE]........................................................................ 
43.3% 86.6% sun.misc.Unsafe.park 
    5.8% 11.6% com.jad.generated.IncrementBench_incrementCustomAtomicWithWork_jmhTest.incrementCustomAtomicWithWork_thrpt_jmhStub 
    0.8% 1.7% org.openjdk.jmh.infra.Blackhole.consumeCPU 
    0.1% 0.1% com.jad.IncrementBench$Worker.work 
    0.0% 0.0% java.lang.Thread.currentThread 
    0.0% 0.0% com.jad.generated.IncrementBench_incrementCustomAtomicWithWork_jmhTest._jmh_tryInit_f_benchmarkparams1_0 
    0.0% 0.0% org.openjdk.jmh.infra.generated.BenchmarkParams_jmhType_B1.<init> 

....[Thread state: TIMED_WAITING]................................................................... 
49.9% 100.0% sun.misc.Unsafe.park 

In "guten Fall":

....[Thread state distributions].................................................................... 
88.2%   TIMED_WAITING 
11.8%   RUNNABLE 

....[Thread state: TIMED_WAITING]................................................................... 
88.2% 100.0% sun.misc.Unsafe.park 

....[Thread state: RUNNABLE]........................................................................ 
    5.6% 47.9% sun.misc.Unsafe.park 
    3.1% 26.3% org.openjdk.jmh.infra.Blackhole.consumeCPU 
    2.4% 20.3% com.jad.generated.IncrementBench_incrementCustomAtomicWithWork_jmhTest.incrementCustomAtomicWithWork_thrpt_jmhStub 
    0.6% 5.5% com.jad.IncrementBench$Worker.work 
    0.0% 0.0% com.jad.generated.IncrementBench_incrementCustomAtomicWithWork_jmhTest.incrementCustomAtomicWithWork_Throughput 
    0.0% 0.0% java.lang.Thread.currentThread 
    0.0% 0.0% org.openjdk.jmh.infra.generated.BenchmarkParams_jmhType_B1.<init> 
    0.0% 0.0% sun.misc.Unsafe.putObject 
    0.0% 0.0% org.openjdk.jmh.runner.InfraControlL2.announceWarmdownReady 
    0.0% 0.0% sun.misc.Unsafe.compareAndSwapInt 

Link to benchmark code

Link to result graphs. X - threads count, Y - thpt, op/microsec

Link to RAW log

UPD

Okay, ich weiß, ich verstehe das, wenn ich ParkNanos benutze, kann ein Thread auch das Schloss (CAS) für längere Zeit halten. Threads mit CAS-Fehler werden in den Ruhezustand versetzt und nur ein Thread arbeitet und erhöht den Wert. Ich sehe, dass für große Nebenläufigkeit, wenn die Arbeit so klein ist - AtomicInteger ist nicht besser Ansatz. Aber wenn wir workSize erhöhen, zum Beispiel auf Stufe = CASThrpt/threadNum, sollte es funktioniert: Für die lokale Maschine I workSize = 300, Ergebnis meiner Prüfung eingestellt haben:

Benchmark          (workSize) Mode Cnt Score Error Units 
IncrementBench.incrementAtomicWithWork    300 thrpt 3 4.133 ± 0.516 ops/us 
IncrementBench.incrementCustomAtomicWithWork   300 thrpt 3 1.883 ± 0.234 ops/us 
IncrementBench.lockIntWithWork      300 thrpt 3 3.831 ± 0.501 ops/us 
IncrementBench.onlyWithWork       300 thrpt 3 4.339 ± 0.243 ops/us 

Atomicinteger - win, lock - zweite Ort, Gewohnheit - Dritter. Aber Problem mit Spikes, immer noch nicht klar. Und ich habe vergessen, Java-Version: Java (TM) SE Laufzeitumgebung (Build 1.7.0_79-b15) Java HotSpot (TM) 64-Bit-Server-VM (Build 24.79-b02, gemischter Modus)

+1

den Anruf zu parkNanos entfernen. Sie möchten so schnell wie möglich erneut iterieren. Stellen Sie außerdem sicher, dass intValue flüchtig ist. Sonst kann ret = intValue nicht den gleichen Wert sehen, den das CAS macht –

+0

'Aber Problem mit Spikes, immer noch nicht klar' Haben Sie Ihre Log-Datei überprüft? Es gibt eine Menge von '' Ereignisse – Ivan

+0

Wenn Sie in den Code anschauen, werden Sie System.exit (0) in @Setup Methode. Es ist für sinnlose Fälle zu entfernen, Beispiel: löschen Sie AtomicInteger mit params workSize (2,4,8 ..). Dieser Fall ist unabhängig vom Parameter. –

Antwort

1

Im Falle von synchronisierten, neigt es dazu, mit Sperren zu verkleben, was bedeutet, dass ein Thread die Sperre für lange Zeiträume halten kann und nicht ein anderer Thread es ziemlich greifen lassen. Das ist sehr schlecht für Multithreading, aber exzellent, wenn Sie einen Benchmark haben, der bessere Ergebnisse bringt, wenn nur ein Thread für relativ lange Zeit läuft.

Sie müssen den Test ändern, so wäre es besser laufen, wenn mehrere Threads verwendet werden als nur ein Thread oder Sie wird in der Tat Tests, die Strategie Arretieren der ärmsten Fairness Politik hat.

Die Locking-Strategie versucht zu justieren, wie das Sperren durchgeführt wird, weshalb es das Verhalten ändern kann, aber es kann keine gute Arbeit leisten, da der Code überhaupt nie Multi-Threading sein sollte.