2012-07-05 25 views
1

我已經用Java編寫了一個(非常簡單的)基準測試程序。它只是將雙值增加到指定的值並花費時間。使用太多線程的問題基準程序

當我在我的6核桌面上使用這種單線程或少量線程(高達100)時,基準測試返回合理且可重複的結果。

但是,當我使用1200線程時,平均多核持續時間顯着低於單數持續時間(約10倍或更多)。無論我使用多少線程,我都確保增量的總量是相同的。

爲什麼性能會隨着線程的增加而下降很多?有解決這個問題的竅門嗎?

我張貼我的來源,但我不認爲,有問題。

Benchmark.java:

package sibbo.benchmark; 

import java.text.DecimalFormat; 
import java.util.LinkedList; 
import java.util.List; 

public class Benchmark implements TestFinishedListener { 
      private static final double TARGET = 1e10; 
    private static final int THREAD_MULTIPLICATOR = 2; 

    public static void main(String[] args) throws InterruptedException { 
     Benchmark b = new Benchmark(TARGET); 
     b.start(); 
    } 

    private int coreCount; 
    private List<Worker> workers = new LinkedList<>(); 
    private List<Worker> finishedWorkers = new LinkedList<>(); 
    private double target; 

    public Benchmark(double target) { 
     this.target = target; 
     getSystemInfos(); 
     printInfos(); 
    } 

    private void getSystemInfos() { 
     coreCount = Runtime.getRuntime().availableProcessors(); 
    } 

    private void printInfos() { 
     System.out.println("Usable cores: " + coreCount); 
     System.out.println("Multicore threads: " + coreCount *     THREAD_MULTIPLICATOR); 
     System.out.println("Loops per core: " + new DecimalFormat("###,###,###,###,##0").format(TARGET)); 

     System.out.println(); 
    } 

    public synchronized void start() throws InterruptedException { 
     Thread.currentThread().setPriority(Thread.MAX_PRIORITY); 

     System.out.print("Initializing singlecore benchmark... "); 
     Worker w = new Worker(this, 0); 
     workers.add(w); 

     Thread.sleep(1000); 
     System.out.println("finished"); 

     System.out.print("Running singlecore benchmark... "); 
     w.runBenchmark(target); 
     wait(); 

     System.out.println("finished"); 
     printResult(); 

     System.out.println(); 
     // Multicore 
     System.out.print("Initializing multicore benchmark... "); 
     finishedWorkers.clear(); 

     for (int i = 0; i < coreCount * THREAD_MULTIPLICATOR; i++) { 
      workers.add(new Worker(this, i)); 
     } 

     Thread.sleep(1000); 
     System.out.println("finished"); 

     System.out.print("Running multicore benchmark... "); 

     for (Worker worker : workers) { 
      worker.runBenchmark(target/THREAD_MULTIPLICATOR); 
     } 

     wait(); 

     System.out.println("finished"); 
     printResult(); 

     Thread.currentThread().setPriority(Thread.NORM_PRIORITY); 
    } 

    private void printResult() { 
     DecimalFormat df = new DecimalFormat("###,###,###,##0.000"); 

     long min = -1, av = 0, max = -1; 
     int threadCount = 0; 
     boolean once = true; 

     System.out.println("Result:"); 

     for (Worker w : finishedWorkers) { 
      if (once) { 
       once = false; 

       min = w.getTime(); 
       max = w.getTime(); 
      } 

      if (w.getTime() > max) { 
       max = w.getTime(); 
      } 

      if (w.getTime() < min) { 
       min = w.getTime(); 
      } 

      threadCount++; 
      av += w.getTime(); 

      if (finishedWorkers.size() <= 6) { 
       System.out.println("Worker " + w.getId() + ": " + df.format(w.getTime()/1e9) + "s"); 
      } 
     } 

     System.out.println("Min: " + df.format(min/1e9) + "s, Max: " + df.format(max/1e9) + "s, Av per Thread: " 
       + df.format((double) av/threadCount/1e9) + "s"); 
    } 

    @Override 
    public synchronized void testFinished(Worker w) { 
     workers.remove(w); 
     finishedWorkers.add(w); 

     if (workers.isEmpty()) { 
      notify(); 
     } 
    } 
} 

Worker.java:

package sibbo.benchmark; 

public class Worker implements Runnable { 
    private double value = 0; 
    private long time; 
    private double target; 
    private TestFinishedListener l; 
    private final int id; 

    public Worker(TestFinishedListener l, int id) { 
     this.l = l; 
     this.id = id; 

     new Thread(this).start(); 
    } 

    public int getId() { 
     return id; 
    } 

    public synchronized void runBenchmark(double target) { 
     this.target = target; 
     notify(); 
    } 

    public long getTime() { 
     return time; 
    } 

    @Override 
    public void run() { 
     synWait(); 
     value = 0; 
     long startTime = System.nanoTime(); 

     while (value < target) { 
      value++; 
     } 

     long endTime = System.nanoTime(); 
     time = endTime - startTime; 

     l.testFinished(this); 
    } 

    private synchronized void synWait() { 
     try { 
      wait(); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 
    } 
} 
+0

我假設你要求解釋_why_它是這樣執行的嗎? – Gray 2012-07-05 14:56:03

+0

當你使用1200個線程運行時,你有沒有機會用完內存?你可以用jconsole觀察你的應用程序,看看內存圖表是否顯示完整的伊甸園和倖存者空間?您可能需要增加-Xmx可用的內存 – Gray 2012-07-05 14:58:01

+0

是的,這正是我感興趣的內容。 – Sibbo 2012-07-05 14:58:20

回答

6

你需要了解的是,OS(或Java線程調度,或兩者)正在嘗試所有的之間的平衡線程在你的應用程序中給他們所有的機會來執行一些工作,並且在線程之間切換的成本是非零的。使用1200個線程,您剛剛達到(也可能遠遠超過)處理器花費更多時間上下文切換的臨界點,而不是實際工作。

這裏是一個粗略的比喻:

你有一個工作在房間A做你站在一個房間,每天8小時,做你的工作。

然後你的老闆過來告訴你,你必須在B房間做一個工作。現在您需要定期離開房間A,沿着大廳走到B房,然後走回去。這種步行每天需要1分鐘。現在你花費3小時,每個工作59.5分鐘,在房間之間步行1分鐘。

現在想象一下,你有1200個房間可以工作。你將花更多的時間在房間之間行走,而不是做實際的工作。這是你把你的處理器放入的情況。它花費了很多時間在上下文之間切換,沒有真正的工作完成。

編輯:現在,根據下面的評論,也許你在每個房間中花費固定的時間量,然後繼續前進,但房間之間的上下文切換次數仍然會影響單個的整體運行時間任務。

+0

同意 - 有了足夠的線程,盒子將花費更多的時間在它們之間跳躍,而不是花在實際運行它們。 – 2012-07-05 15:07:56

+0

沒有解釋爲什麼singlecore版本因線程數量過多而受到的影響較少 – TeaOverflow 2012-07-05 15:29:12

+1

@Evgeni我假設OP在此濫用術語「單核」來表示「單線程」,這是基於他在代碼中使用該術語。 – 2012-07-05 15:36:34

1

好吧,我想我已經找到了我的問題,但直到現在,沒有解決方案。

當測量每個線程運行的時間以完成他的工作時,對於不同總線程數有不同的可能最小值。最大值每次都是一樣的。如果線程先啓動然後經常暫停並最後結束。例如,這個最大值可能是10秒。假設每個線程完成的操作總數保持不變,無論使用多少線程,當使用不同數量的線程時,必須更改由單個線程完成的操作量。例如,使用一個線程,它必須執行1000次操作,但使用10個線程,每個人都必須執行100次操作。現在,使用十個線程,一個線程可以使用的最少時間遠遠少於使用一個線程的時間。因此,計算每個線程執行工作所需的平均時間是無稽之談。最少使用十個線程將是1秒。如果一個線程不中斷地工作,就會發生這種情況。

編輯

的解決辦法是簡單地測量的第一線的開始和最後的完成之間的時間量。

+0

每個核心的線程越多,每個線程完成的時間就越長。一旦你擁有比內核更多的線程,即使是端到端的時間也應該增加。 – 2012-07-05 15:42:07