1. 序章
このチュートリアルでは、従来のJDKコレクションとEclipseコレクションのパフォーマンスを比較します。 さまざまなシナリオを作成し、結果を調査します。
2. 構成
まず、この記事では、デフォルトの構成を使用してテストを実行することに注意してください。 ベンチマークにはフラグやその他のパラメータは設定されません。
次のハードウェアとライブラリを使用します。
- JDK 11.0.3、Java HotSpot(TM)64ビットサーバーVM、11.0.3+12-LTS。
- MacPro 2.6GHz 6コアi7、16GBDDR4。
- Eclipseコレクション10.0.0(執筆時点で入手可能な最新のもの)
- JMH(Java Microbenchmark Harness)を活用してベンチマークを実行します
- JMHビジュアライザーは、JMHの結果からチャートを生成します
プロジェクトを作成する最も簡単な方法は、コマンドラインを使用することです。
mvn archetype:generate \
-DinteractiveMode=false \
-DarchetypeGroupId=org.openjdk.jmh \
-DarchetypeArtifactId=jmh-java-benchmark-archetype \
-DgroupId=com.baeldung \
-DartifactId=benchmark \
-Dversion=1.0
その後、お気に入りのIDEを使用してプロジェクトを開き、 pom.xml を編集して、Eclipseコレクションの依存関係を追加できます。
<dependency>
<groupId>org.eclipse.collections</groupId>
<artifactId>eclipse-collections</artifactId>
<version>10.0.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.collections</groupId>
<artifactId>eclipse-collections-api</artifactId>
<version>10.0.0</version>
</dependency>
3. 最初のベンチマーク
最初のベンチマークは単純です。 以前に作成したリストof整数の合計を計算します。
シリアルおよびパラレルで実行しながら、6つの異なる組み合わせをテストします。
private List<Integer> jdkIntList;
private MutableList<Integer> ecMutableList;
private ExecutorService executor;
private IntList ecIntList;
@Setup
public void setup() {
PrimitiveIterator.OfInt iterator = new Random(1L).ints(-10000, 10000).iterator();
ecMutableList = FastList.newWithNValues(1_000_000, iterator::nextInt);
jdkIntList = new ArrayList<>(1_000_000);
jdkIntList.addAll(ecMutableList);
ecIntList = ecMutableList.collectInt(i -> i, new IntArrayList(1_000_000));
executor = Executors.newWorkStealingPool();
}
@Benchmark
public long jdkList() {
return jdkIntList.stream().mapToLong(i -> i).sum();
}
@Benchmark
public long ecMutableList() {
return ecMutableList.sumOfInt(i -> i);
}
@Benchmark
public long jdkListParallel() {
return jdkIntList.parallelStream().mapToLong(i -> i).sum();
}
@Benchmark
public long ecMutableListParallel() {
return ecMutableList.asParallel(executor, 100_000).sumOfInt(i -> i);
}
@Benchmark
public long ecPrimitive() {
return this.ecIntList.sum();
}
@Benchmark
public long ecPrimitiveParallel() {
return this.ecIntList.primitiveParallelStream().sum();
}
最初のベンチマークを実行するには、以下を実行する必要があります。
mvn clean install
java -jar target/benchmarks.jar IntegerListSum -rf json
これにより、 IntegerListSum クラスでベンチマークがトリガーされ、結果がJSONファイルに保存されます。
テストではスループットまたは1秒あたりの操作数を測定するため、高いほど良いです。
Benchmark Mode Cnt Score Error Units
IntegerListSum.ecMutableList thrpt 10 573.016 ± 35.865 ops/s
IntegerListSum.ecMutableListParallel thrpt 10 1251.353 ± 705.196 ops/s
IntegerListSum.ecPrimitive thrpt 10 4067.901 ± 258.574 ops/s
IntegerListSum.ecPrimitiveParallel thrpt 10 8827.092 ± 11143.823 ops/s
IntegerListSum.jdkList thrpt 10 568.696 ± 7.951 ops/s
IntegerListSum.jdkListParallel thrpt 10 918.512 ± 27.487 ops/s
私たちのテストによると、Eclipseコレクションのプリミティブの並列リストはすべての中で最高のスループットを示しました。 また、これは最も効率的であり、並列で実行されているJavaJDKのほぼ10倍のパフォーマンスでした。
もちろん、その一部は、プリミティブリストを使用する場合、ボクシングとアンボクシングに関連するコストがないという事実によって説明できます。
JMHVisualizerを使用して結果を分析できます。 以下のチャートは、より良い視覚化を示しています。
4. フィルタリング
次に、リストを変更して、5の倍数であるすべての要素を取得します。 以前のベンチマークの大部分とフィルター関数を再利用します。
private List<Integer> jdkIntList;
private MutableList<Integer> ecMutableList;
private IntList ecIntList;
private ExecutorService executor;
@Setup
public void setup() {
PrimitiveIterator.OfInt iterator = new Random(1L).ints(-10000, 10000).iterator();
ecMutableList = FastList.newWithNValues(1_000_000, iterator::nextInt);
jdkIntList = new ArrayList<>(1_000_000);
jdkIntList.addAll(ecMutableList);
ecIntList = ecMutableList.collectInt(i -> i, new IntArrayList(1_000_000));
executor = Executors.newWorkStealingPool();
}
@Benchmark
public List<Integer> jdkList() {
return jdkIntList.stream().filter(i -> i % 5 == 0).collect(Collectors.toList());
}
@Benchmark
public MutableList<Integer> ecMutableList() {
return ecMutableList.select(i -> i % 5 == 0);
}
@Benchmark
public List<Integer> jdkListParallel() {
return jdkIntList.parallelStream().filter(i -> i % 5 == 0).collect(Collectors.toList());
}
@Benchmark
public MutableList<Integer> ecMutableListParallel() {
return ecMutableList.asParallel(executor, 100_000).select(i -> i % 5 == 0).toList();
}
@Benchmark
public IntList ecPrimitive() {
return this.ecIntList.select(i -> i % 5 == 0);
}
@Benchmark
public IntList ecPrimitiveParallel() {
return this.ecIntList.primitiveParallelStream()
.filter(i -> i % 5 == 0)
.collect(IntLists.mutable::empty, MutableIntList::add, MutableIntList::addAll);
}
前と同じようにテストを実行します。
mvn clean install
java -jar target/benchmarks.jar IntegerListFilter -rf json
そして結果:
Benchmark Mode Cnt Score Error Units
IntegerListFilter.ecMutableList thrpt 10 145.733 ± 7.000 ops/s
IntegerListFilter.ecMutableListParallel thrpt 10 603.191 ± 24.799 ops/s
IntegerListFilter.ecPrimitive thrpt 10 232.873 ± 8.032 ops/s
IntegerListFilter.ecPrimitiveParallel thrpt 10 1029.481 ± 50.570 ops/s
IntegerListFilter.jdkList thrpt 10 155.284 ± 4.562 ops/s
IntegerListFilter.jdkListParallel thrpt 10 445.737 ± 23.685 ops/s
ご覧のとおり、EclipseCollectionsPrimitiveが再び勝者となりました。 JDK並列リストの2倍以上のスループットを実現します。
フィルタリングの場合、並列処理の効果がより明確になることに注意してください。 加算はCPUにとって安価な操作であり、シリアルとパラレルの間に同じ違いは見られません。
また、Eclipse Collectionsプリミティブリストが以前に取得したパフォーマンスの向上は、各要素で行われた作業がボクシングとアンボクシングのコストを上回り始めるにつれて蒸発し始めます。
最後に、プリミティブに対する操作はオブジェクトよりも高速であることがわかります。
5. 結論
この記事では、JavaコレクションとEclipseコレクションを比較するためのベンチマークをいくつか作成しました。 JMHを活用して、環境バイアスを最小限に抑えようとしました。
いつものように、ソースコードはGitHubでから入手できます。