2016-12-29 47 views
4

我正在玩無限的流,並制定了這個程序進行基準測試。基本上你提供的號碼越大,它的結束速度就越快。然而,我很驚訝地發現,與順序流相比,使用parellel流導致性能呈指數級惡化。直觀上,人們會期望在多線程環境中生成和評估無限數量的隨機數字,但似乎並非如此。爲什麼是這樣?爲什麼並行流更慢?

final int target = Integer.parseInt(args[0]); 
    if (target <= 0) { 
     System.err.println("Target must be between 1 and 2147483647"); 
     return; 
    } 

    final long startTime, endTime; 
    startTime = System.currentTimeMillis(); 

    System.out.println(
     IntStream.generate(() -> new Double(Math.random()*2147483647).intValue()) 
     //.parallel() 
     .filter(i -> i <= target) 
     .findFirst() 
     .getAsInt() 
    ); 

    endTime = System.currentTimeMillis(); 
    System.out.println("Execution time: "+(endTime-startTime)+" ms"); 
+5

並行化的小任務總是比較慢。多線程有足夠的開銷,任務需要證明成本,否則你不會看到任何收益。另外,1個測試是沒有意義的。至少,堅持這個循環,並取平均水平。 – Carcigenicate

+2

@Carcigenicate除此之外,我相信Math.random()減慢它:-)檢查我的答案,如果感興趣。 –

+0

玩-Djava.util.concurrent.ForkJoinPool.common.parallelism = ,更改公共池並觀察結果 – gaston

回答

6

我完全同意其他評論和答案,但實際上你的測試在目標非常低的情況下表現很奇怪。在我的小型筆記本電腦上,平行版本的平均速度比目標低時低60倍。這種極端的差異無法通過流API中並行化的開銷來解釋,所以我也非常驚訝:-)。 IMO罪魁禍首就在這裏:

Math.random() 

內部此調用依賴於java.util.Random全局實例。在documentation of Random中寫爲:

java.util.Random的實例是線程安全的。但是,跨線程使用同一個java.util.Random實例的併發 可能會遇到 爭用以及隨之而來的糟糕的性能。請考慮在多線程設計中使用 ThreadLocalRandom。

所以我認爲,與順序執行相比,並行執行的性能非常差,這是由隨機線程爭用解釋的,而不是任何其他開銷。如果您使用ThreadLocalRandom(根據文檔中的建議),則性能差異不會那麼顯着。另一種選擇是實施更高級的號碼供應商。

+0

查看'Math.random()'的內部實現 - 它是用原子完成的,沒有任何鎖。很難相信它引入了這樣的差異 –

+3

Yeap,我剛剛檢查過它。但是,與線程本地隨機相比,它的確帶來了巨大的差異,就像文檔所說的那樣......:O –

+1

@SergeyFedorov,儘管atomics沒有鎖定,但他們可以大大降低原子操作的do-while循環中的性能,直到考慮狀態是一致的。問題是Unsafe.compareAndSwap() - 他們在裏面使用繁忙的等待。在高爭用情況下,成本會呈指數級增長,並且一組操作的同步塊會更好。只有測試才能說明最佳策略。那麼,當然,ThreadLocalRandom每次都會贏得兩種方法。 –

0

將工作傳遞給多個線程的代價在你第一次執行時花費很大。這個成本是相當固定的,所以即使你的任務微不足道,開銷也相對較高。

您遇到的問題之一就是非常低效的代碼是確定解決方案執行情況的一個非常糟糕的方法。另外,第一次運行的方式以及它在幾秒後的運行方式通常可能有100倍的差異(可以更多)。我建議使用一個已經最優化的例子,然後嘗試使用多個線程。

例如

long start = System.nanoTime(); 
int value = (int) (Math.random() * (target+1L)); 
long time = System.nanoTime() - value; 
// don't time IO as it is sooo much slower 
System.out.println(value); 

注意:直到代碼已經預熱並編譯完成纔會有效。即忽略該代碼運行的前2-5秒。

0

根據各種答案的建議,我想我已經修復了它。我不確定具體的瓶頸是什麼,但是在i5-4590T上,具有以下代碼的並行版本的執行速度比順序版本更快。爲了簡便起見,我只包括(重構)代碼的相關部分:

static IntStream getComputation() { 
    return IntStream 
      .generate(() -> ThreadLocalRandom.current().nextInt(2147483647)); 
} 

static void computeSequential(int target) { 
    for (int loop = 0; loop < target; loop++) { 
     final int result = getComputation() 
        .filter(i -> i <= target) 
        .findAny() 
        .getAsInt(); 
     System.out.println(result); 
    } 
} 

static void computeParallel(int target) { 
    IntStream.range(0, target) 
       .parallel() 
       .forEach(loop -> { 
        final int result = getComputation() 
         .parallel() 
         .filter(i -> i <= target) 
         .findAny() 
         .getAsInt(); 
        System.out.println(result); 
       }); 
} 

編輯:我還應該注意到,我把它們都放在一個循環來獲得更長的運行時間。

+2

這不是一個有效的測試,因爲您現在正在另一個並行操作中執行並行流操作,該操作會將內部操作(您的問題的實際操作)基本轉變爲順序操作,因爲工作被分割爲完全不同的方式,即範圍'IntStream.range(0,target)'將被並行處理,而不是隨機數字序列。 – Holger

+0

@Holger但在多臺機器上測試之後,我仍然得到了我期望的結果。在四核CPU上,對於大多數1000以下的數字,並行版本的執行速度比順序版本快3倍以上。 –

+1

其實,我嘗試將.parallel()添加到順序版本,並且它仍然較慢,所以加速是來自InstStream.range而不是隨機數字流。 –