新しい Java ストリームをいじっているうちに、並列ストリームのパフォーマンスに関連する奇妙なことに気付きました。テキスト ファイルから単語を読み取り、長さが 5 を超える単語をカウントする単純なプログラムを使用しました (テスト ファイルには 30000 単語があります)。
String contents = new String(Files.readAllBytes(Paths.get("text.txt")));
List<String> words = Arrays.asList(contents.split("[\\P{L}]+"));
long startTime;
for (int i = 0; i < 100; i++) {
startTime = System.nanoTime();
words.parallelStream().filter(w -> w.length() > 5).count();
System.out.println("Time elapsed [PAR]: " + (System.nanoTime() - startTime));
startTime = System.nanoTime();
words.stream().filter(w -> w.length() > 5).count();
System.out.println("Time elapsed [SEQ]: " + (System.nanoTime() - startTime));
System.out.println("------------------");
}
これにより、私のマシンで次の出力が生成されます (最初と最後の 5 回のループ反復についてのみ言及します)。
Time elapsed [PAR]: 114185196
Time elapsed [SEQ]: 3222664
------------------
Time elapsed [PAR]: 569611
Time elapsed [SEQ]: 797113
------------------
Time elapsed [PAR]: 678231
Time elapsed [SEQ]: 414807
------------------
Time elapsed [PAR]: 755633
Time elapsed [SEQ]: 679085
------------------
Time elapsed [PAR]: 755633
Time elapsed [SEQ]: 393425
------------------
...
Time elapsed [PAR]: 90232
Time elapsed [SEQ]: 163785
------------------
Time elapsed [PAR]: 80396
Time elapsed [SEQ]: 154805
------------------
Time elapsed [PAR]: 83817
Time elapsed [SEQ]: 154377
------------------
Time elapsed [PAR]: 81679
Time elapsed [SEQ]: 186449
------------------
Time elapsed [PAR]: 68849
Time elapsed [SEQ]: 154804
------------------
最初の処理が他の処理よりも 100 倍遅いのはなぜですか? 最初の反復では並列ストリームが順次ストリームよりも遅いのに、最後の反復では 2 倍の速度になるのはなぜですか? シーケンシャル ストリームとパラレル ストリームの両方が時間の経過とともに高速になるのはなぜですか? これはループの最適化に関連していますか?
後で編集: Luigi の提案で、JUnitBenchmarksを使用してベンチマークを実装しました。
List<String> words = null;
@Before
public void setup() {
try {
String contents = new String(Files.readAllBytes(Paths.get("text.txt")));
words = Arrays.asList(contents.split("[\\P{L}]+"));
} catch (IOException e) {
e.printStackTrace();
}
}
@BenchmarkOptions(benchmarkRounds = 100)
@Test
public void parallelTest() {
words.parallelStream().filter(w -> w.length() > 5).count();
}
@BenchmarkOptions(benchmarkRounds = 100)
@Test
public void sequentialTest() {
words.stream().filter(w -> w.length() > 5).count();
}
また、テスト ファイルの単語数を 300000 に増やしました。新しい結果は次のとおりです。
Benchmark.sequentialTest: [105 ラウンド中 100 ラウンドを測定、スレッド: 1 (連続)]
round: 0.08 [+- 0.04]、round.block: 0.00 [+- 0.00]、round.gc: 0.00 [+- 0.00]、GC.calls: 62、GC.time: 1.53、time.total: 8.65、時間.ウォームアップ: 0.81、タイム.ベンチ: 7.85
Benchmark.parallelTest: [105 ラウンド中 100 ラウンドを測定、スレッド: 1 (順次)]
round: 0.06 [+- 0.02]、round.block: 0.00 [+- 0.00]、round.gc: 0.00 [+- 0.00]、GC.calls: 32、GC.time: 0.79、time.total: 6.82、時間.ウォームアップ: 0.39、タイム.ベンチ: 6.43
したがって、最初の結果は、間違ったマイクロベンチマーク構成が原因だったようです...