2011-08-23 50 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 

編輯:性能遭受由於的Math.random()是一個同步的方法。與新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不同步,它調用Random.next(int),後者又調用AtomicLong.compareAndSet ..我不明白爲什麼這會影響性能 – GantengX

+4

我猜想,因爲你只是回到讓多個線程再次爭用相同的資源:在這種情況下的AtomicLong。一次只有一個線程可以更新其值,並且每次調用nextDouble()時都會更新兩次。 –

3

你會很好地閱讀另一個線程的內容。那裏有很多好的提示。

也許你的基準測試最重要的問題是,根據Math.random()合同,「這個方法被正確地同步以允許多個線程正確使用,但是如果許多線程需要生成僞隨機數以很快的速度,它可以減少爭用每個線程擁有自己的僞隨機數發生器「

閱讀此:該方法是同步的,所以只有一個線程可能能夠有效地使用它在相同時間。因此,你需要花費大量的時間來分配任務,而只是強制它們再次連續運行。

1

當您使用多個線程時,您需要知道使用其他線程的開銷。您還需要確定您的算法是否具有可以並行執行的工作。所以你需要有可以同時運行的工作,這個工作足夠大,會超過使用多線程的開銷。

在這種情況下,最簡單的解決方法是在每個線程中使用單獨的隨機數。你所面臨的問題是,作爲一個微基準,你的循環實際上並沒有做任何事情,JIT非常善於丟棄沒有任何作用的代碼。解決方法是對隨機結果進行求和並將其從call()中返回,因爲這通常足以防止JIT丟棄代碼。

最後,如果你想總結很多數字,你不需要保存它們並在以後進行總結。你可以在你走的時候將它們相加。