2011-08-23 6 views
5

私は単純な計算を実行しようとしています(Math.random() 10000000回を呼び出しています)。意外なことに、単純なメソッドで実行すると、ExecutorServiceを使用するよりもはるかに高速に実行されます。ExecutorServiceスローマルチスレッドパフォーマンス

私はどのように私は私の現在のコードに基づいてパフォーマンスを向上させるん

ExecutorService's surprising performance break-even point --- rules of thumb?で別のスレッドを読んで、バッチを使用してCallableを実行することで答えを追跡しようとしましたが、パフォーマンスはまだ悪いですか?

import java.util.*; 
import java.util.concurrent.*; 

public class MainTest { 
    public static void main(String[]args) throws Exception { 
     new MainTest().start();; 
    } 

    final List<Worker> workermulti = new ArrayList<Worker>(); 
    final List<Worker> workersingle = new ArrayList<Worker>(); 
    final int count=10000000; 

    public void start() throws Exception { 
     int n=2; 

     workersingle.add(new Worker(1)); 
     for (int i=0;i<n;i++) { 
      // worker will only do count/n job 
      workermulti.add(new Worker(n)); 
     } 

     ExecutorService serviceSingle = Executors.newSingleThreadExecutor(); 
     ExecutorService serviceMulti = Executors.newFixedThreadPool(n); 
     long s,e; 
     int tests=10; 
     List<Long> simple = new ArrayList<Long>(); 
     List<Long> single = new ArrayList<Long>(); 
     List<Long> multi = new ArrayList<Long>(); 

     for (int i=0;i<tests;i++) { 
      // simple 
      s = System.currentTimeMillis(); 
      simple(); 
      e = System.currentTimeMillis(); 
      simple.add(e-s); 

      // single thread 
      s = System.currentTimeMillis(); 
       serviceSingle.invokeAll(workersingle); // single thread 
      e = System.currentTimeMillis(); 
      single.add(e-s); 

      // multi thread 
      s = System.currentTimeMillis(); 
       serviceMulti.invokeAll(workermulti); 
      e = System.currentTimeMillis(); 
      multi.add(e-s); 
     } 
     long avgSimple=sum(simple)/tests; 
     long avgSingle=sum(single)/tests; 
     long avgMulti=sum(multi)/tests; 
     System.out.println("Average simple: "+avgSimple+" ms"); 
     System.out.println("Average single thread: "+avgSingle+" ms"); 
     System.out.println("Average multi thread: "+avgMulti+" ms"); 

     serviceSingle.shutdown(); 
     serviceMulti.shutdown(); 
    } 

    long sum(List<Long> list) { 
     long sum=0; 
     for (long l : list) { 
      sum+=l; 
     } 
     return sum; 
    } 

    private void simple() { 
     for (int i=0;i<count;i++){ 
      Math.random(); 
     } 
    } 

    class Worker implements Callable<Void> { 
     int n; 

     public Worker(int n) { 
      this.n=n; 
     } 

     @Override 
     public Void call() throws Exception { 
      // divide count with n to perform batch execution 
      for (int i=0;i<(count/n);i++) { 
       Math.random(); 
      } 
      return null; 
     } 
    } 
} 

このコード

Average simple: 920 ms 
Average single thread: 1034 ms 
Average multi thread: 1393 ms 

EDIT用出力:各スレッドのための新しいランダムオブジェクトにMath.random()を変更した後の性能が原因Math.random(に苦しむ)である同期方法.. 、パフォーマンスが(スレッドごとにランダムでMath.random()を交換した後)

新しいコードの出力

を向上

Average simple: 928 ms 
Average single thread: 1046 ms 
Average multi thread: 642 ms 

答えて

12

Math.random()が同期しています。同期の全体的なポイントの種類は、物事を遅くして衝突しないようにすることです。同期化されていないものを使用するか、または新しいスレッドRandomのように、各スレッドに独自のオブジェクトを割り当てます。

+0

あなたは正しいです!私はMath.random()が同期されていることを認識しませんでした。一度、各Workerに新しいRandomオブジェクトを配置すると、パフォーマンスは大幅に向上しました – GantengX

+0

Randomオブジェクトを共有しようとすると、パフォーマンスはまだ損なわれます。なぜこれがそうであるか知っていますか? Random.nextDoubleは同期されておらず、AtomicLong.compareAndSetを呼び出すRandom.next(int)を呼び出します。私はなぜこれがパフォーマンスに影響するのかわかりません – GantengX

+4

同じリソースに対して複数のスレッドが再び競合していることに戻ります。この場合はAtomicLongです。一度に1つのスレッドだけがその値を更新でき、nextDouble()の呼び出しごとに2回更新されます。 –

3

他のスレッドの内容を読むのはうまくいくでしょう。そこには良いヒントがたくさんあります。

おそらく、ベンチマークで最も重大な問題は、Math.random()コントラクトによれば、「このメソッドは複数のスレッドで正しく使用できるように正しく同期されますが、多くのスレッドで擬似乱数非常に速く、各スレッドの擬似乱数ジェネレータを持つための競合を減らすことができる "

このメソッドは同期されているため、同じスレッドで有効に使用できる可能性が高い時間。したがって、タスクを分散するためにオーバーヘッドのオーバーヘッドを行い、それらを再度強制的に実行するようにします。

1

複数のスレッドを使用する場合、追加のスレッドを使用するオーバーヘッドに注意する必要があります。また、あなたのアルゴリズムが並列に実行できるかどうかを判断する必要があります。したがって、同時に実行できる作業が必要です。これは、複数のスレッドを使用するオーバーヘッドを超えるほど十分です。

この場合、最も簡単な回避策は、各スレッドで別々のランダムを使用することです。あなたが持っている問題は、マイクロベンチマークとして、あなたのループは実際に何もしないし、JITは何もしないコードを捨てることに非常に優れているということです。これを回避するには、ランダムな結果を合計してcall()から返します。これは通常、JITがコードを破棄しないようにするためです。

最後に、たくさんの数値を合計する場合は、数値を保存して後で合計する必要はありません。あなたが行くようにそれらを合計することができます。

関連する問題