2015-12-16 4 views
7

Ich lernte Multithreading und fand die Verlangsamung von Object.hashCode in Multi-Thread-Umgebung, da es doppelt so lange dauert, um den Standard-Hash-Code mit 4 Threads vs 1 Thread für die gleiche Zahl zu berechnen von Objekten.Bench Mark in Multi-Thread-Umgebung

Aber nach meinem Verständnis sollte es eine ähnliche Menge an Zeit parallel dazu tun.

Sie können die Anzahl der Threads ändern. Jeder Thread hat die gleiche Menge an Arbeit zu tun, so dass Sie hoffen, dass die Ausführung von 4 Threads auf einem Computer, der Quad-Core-Rechner ist, ungefähr die gleiche Zeit wie das Ausführen eines einzelnen Threads dauern kann.

Ich sehe ~ 2,3 Sekunden für 4x aber .9 s für 1x.

Gibt es eine Lücke in meinem Verständnis, bitte helfen Sie mir, dieses Verhalten zu verstehen.

import java.util.Arrays; 
import java.util.List; 
import java.util.concurrent.Callable; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 
import java.util.concurrent.Future; 
import java.util.concurrent.ThreadFactory; 

public class ObjectHashCodePerformance { 

private static final int THREAD_COUNT = 4; 
private static final int ITERATIONS = 20000000; 

public static void main(final String[] args) throws Exception { 
    long start = System.currentTimeMillis(); 
    new ObjectHashCodePerformance().run(); 
    System.err.println(System.currentTimeMillis() - start); 
} 

private final ExecutorService _sevice = Executors.newFixedThreadPool(THREAD_COUNT, 
     new ThreadFactory() { 
      private final ThreadFactory _delegate = Executors.defaultThreadFactory(); 

      @Override 
      public Thread newThread(final Runnable r) { 
       Thread thread = _delegate.newThread(r); 
       thread.setDaemon(true); 
       return thread; 
      } 
     }); 

    private void run() throws Exception { 
    Callable<Void> work = new java.util.concurrent.Callable<Void>() { 
     @Override 
     public Void call() throws Exception { 
      for (int i = 0; i < ITERATIONS; i++) { 
       Object object = new Object(); 
       object.hashCode(); 
      } 
      return null; 
     } 
    }; 
    @SuppressWarnings("unchecked") 
    Callable<Void>[] allWork = new Callable[THREAD_COUNT]; 
    Arrays.fill(allWork, work); 
    List<Future<Void>> futures = _sevice.invokeAll(Arrays.asList(allWork)); 
    for (Future<Void> future : futures) { 
     future.get(); 
    } 
} 

} 

Für Fadenzahl 4 Ausgang ist

~2.3 seconds 

Für Fadenzahl 1 Ausgang

ist
~.9 seconds 
+0

Teilen Sie die Änderungen, die Sie zwischen 1 und 4 Threads – Jan

+0

Die Zeitmessung sagen nicht unbedingt Sie viel hier gemacht. Siehe http://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java – Marco13

+1

Vermutlich messen Sie nicht das Richtige: GC, Erstellung der Executoren und seiner Threads, Thread-Koordination, Objekt-Instanziierungen, Speicherzuordnungen usw.Wie dem auch sei, das Beanchmark ist ziemlich nutzlos, da Sie sowieso nichts in die hashCode() - Implementierung von Object ändern können. –

Antwort

6

ich eine einfache JMH Benchmark erstellt haben, die verschiedenen Fälle zu testen:

@Fork(1) 
@State(Scope.Benchmark) 
@OutputTimeUnit(TimeUnit.NANOSECONDS) 
@Measurement(iterations = 10) 
@Warmup(iterations = 10) 
@BenchmarkMode(Mode.AverageTime) 
public class HashCodeBenchmark { 
    private final Object object = new Object(); 

    @Benchmark 
    @Threads(1) 
    public void singleThread(Blackhole blackhole){ 
     blackhole.consume(object.hashCode()); 
    } 

    @Benchmark 
    @Threads(2) 
    public void twoThreads(Blackhole blackhole){ 
     blackhole.consume(object.hashCode()); 
    } 

    @Benchmark 
    @Threads(4) 
    public void fourThreads(Blackhole blackhole){ 
     blackhole.consume(object.hashCode()); 
    } 

    @Benchmark 
    @Threads(8) 
    public void eightThreads(Blackhole blackhole){ 
     blackhole.consume(object.hashCode()); 
    } 
} 

Und die Ergebnisse sind wie folgt:

Benchmark      Mode Cnt Score Error Units 
HashCodeBenchmark.eightThreads avgt 10 5.710 ± 0.087 ns/op 
HashCodeBenchmark.fourThreads avgt 10 3.603 ± 0.169 ns/op 
HashCodeBenchmark.singleThread avgt 10 3.063 ± 0.011 ns/op 
HashCodeBenchmark.twoThreads avgt 10 3.067 ± 0.034 ns/op 

so können wir das so lange sehen, da es keine mehr Threads als Kerne sind, die Zeit pro hashcode gleich bleibt.

PS: Wie @Tom Cools kommentiert hat - messen Sie die Zuteilungsgeschwindigkeit und nicht die HashCode() Geschwindigkeit in Ihrem Test.

+0

Vielen Dank für die Analyse ... :) –

+0

Können Sie bitte sagen ..about Tool verwendet für Bankmarkierung –

+1

es heißt JMH: http://openjdk.java.net/projects/code-tools/jmh/ –

1

See Palamino Kommentar:

Sie sind nicht hashCode Messung(), Sie messen die Instanziierung von 20 Millionen Objekten beim einzelnen Thread und 80 Millionen Objekte beim Ausführen von 4 Fäden. Bewegen Sie den neuen Object() Logik aus der for-Schleife in Ihrer Callable, dann werden Sie messen hashCode() - Palamino

+2

Er sagte, dass Sie die Thread-Anzahl ändern können, um das Problem zu beobachten, dass er beschrieben – Marco13

+0

Ich zog es aus demselben Ergebnis .. :( –

0

Zwei Problem, das ich mit dem Code sehen:

  1. Die Größe Allwork [] Array gleich ITERATIONS.
  2. Und während der Iteration, in der Call() - Methode sicherstellen, dass jeder Thread erhält seinen Anteil der Last. ITERATIONS/THREAD_COUNT.

Unten ist die modifizierte Version, die Sie ausprobieren können:

import java.util.Arrays; 
import java.util.List; 
import java.util.concurrent.Callable; 
import java.util.concurrent.CountDownLatch; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 
import java.util.concurrent.Future; 
import java.util.concurrent.ThreadFactory; 

public class ObjectHashCodePerformance { 

private static final int THREAD_COUNT = 1; 
private static final int ITERATIONS = 20000; 
private final Object object = new Object(); 

public static void main(final String[] args) throws Exception { 
    long start = System.currentTimeMillis(); 
    new ObjectHashCodePerformance().run(); 
    System.err.println(System.currentTimeMillis() - start); 
} 

private final ExecutorService _sevice = Executors.newFixedThreadPool(THREAD_COUNT, 
     new ThreadFactory() { 
      private final ThreadFactory _delegate = Executors.defaultThreadFactory(); 

      @Override 
      public Thread newThread(final Runnable r) { 
       Thread thread = _delegate.newThread(r); 
       thread.setDaemon(true); 
       return thread; 
      } 
     }); 

    private void run() throws Exception { 
    Callable<Void> work = new java.util.concurrent.Callable<Void>() { 
     @Override 
     public Void call() throws Exception { 
      for (int i = 0; i < ITERATIONS/THREAD_COUNT; i++) { 
       object.hashCode(); 
      } 
      return null; 
     } 
    }; 
    @SuppressWarnings("unchecked") 
    Callable<Void>[] allWork = new Callable[ITERATIONS]; 
    Arrays.fill(allWork, work); 
    List<Future<Void>> futures = _sevice.invokeAll(Arrays.asList(allWork)); 
    System.out.println("Futures size : " + futures.size()); 
    for (Future<Void> future : futures) { 
     future.get(); 
    } 
} 

} 
+1

in der 'run()/call()' Methode, die Sie noch Objekte zuweisen - so messen Sie die Hashcode plus die Zuteilungsgeschwindigkeit. Deine Antwort ist fehlerhaft. –

+1

@SvetlinZarev Punkt genommen und den Code aktualisiert. –