1. 概要

このチュートリアルでは、Javaで人気のあるいくつかのプリミティブリストライブラリのパフォーマンスを比較します。

そのために、各ライブラリの add()、get()、、および contains()メソッドをテストします。

2. パフォーマンスの比較

それでは、どのライブラリが高速で動作するプリミティブコレクションAPIを提供しているかを調べてみましょう。

そのために、 Trove、Fastutil 、およびColtListアナログを比較してみましょう。 JMH (Java Microbenchmark Harness)ツールを使用して、パフォーマンステストを作成します。

2.1. JMHパラメータ

次のパラメータを使用してベンチマークテストを実行します。

@BenchmarkMode(Mode.SingleShotTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Measurement(batchSize = 100000, iterations = 10)
@Warmup(batchSize = 100000, iterations = 10)
@State(Scope.Thread)
public class PrimitivesListPerformance {
}

ここでは、各ベンチマークメソッドの実行時間を測定します。また、結果をミリ秒単位で表示します。

@State アノテーションは、クラスで宣言された変数がベンチマークテストの実行の一部ではないことを示します。 ただし、ベンチマークメソッドでそれらを使用できます。

さらに、プリミティブのリストを定義して初期化します。

public static class PrimitivesListPerformance {
    private List<Integer> arrayList = new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9));
    private TIntArrayList tList = new TIntArrayList(new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9});
    private cern.colt.list.IntArrayList coltList = new cern.colt.list.IntArrayList(new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9});
    private IntArrayList fastUtilList = new IntArrayList(new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9});

    private int getValue = 4;
}

これで、ベンチマークを作成する準備が整いました。

3. add()

まず、プリミティブリストに要素を追加してテストしましょう。 また、コントロールとしてArrayList用に1つ追加します。

3.1. ベンチマークテスト

最初のマイクロベンチマークは、 ArrayList’ s add()メソッド用です。

@Benchmark
public boolean addArrayList() {
    return arrayList.add(getValue);
}

同様に、Troveの TIntArrayList.add()の場合:

@Benchmark
public boolean addTroveIntList() {
    return tList.add(getValue);
}

同様に、Coltの IntArrayList.add()は次のようになります。

@Benchmark
public void addColtIntList() {
    coltList.add(getValue);
}

また、Fastutilライブラリの場合、 IntArrayList.add()メソッドのベンチマークは次のようになります。

@Benchmark
public boolean addFastUtilIntList() {
    return fastUtilList.add(getValue);
}

3.2. 試験結果

次に、実行して結果を比較します。

Benchmark           Mode  Cnt  Score   Error  Units
addArrayList          ss   10  4.527 ± 4.866  ms/op
addColtIntList        ss   10  1.823 ± 4.360  ms/op
addFastUtilIntList    ss   10  2.097 ± 2.329  ms/op
addTroveIntList       ss   10  3.069 ± 4.026  ms/op

結果から、 ArrayListのadd()が最も遅いオプションであることがはっきりとわかります。

プリミティブリストライブラリの記事で説明したように、これは論理的です。 ArrayList は、ボクシング/オートボクシングを使用して、コレクション内にint値を格納します。 したがって、ここでは大幅な速度低下が発生します。

一方、ColtとFastutilの add()メソッドが最速でした。

内部的には、3つのライブラリすべてが int[]内に値を格納します。 では、なぜ add()メソッドの実行時間が異なるのでしょうか。

答えは、デフォルトの容量がいっぱいになったときに int[]をどのように拡張するかです。

  • Coltは、いっぱいになったときにのみ内部 int[]を成長させます
  • 対照的に、 TroveとFastutilは、 int [] コンテナを拡張するときに、いくつかの追加の計算を使用します

そのため、Coltがテスト結果で勝っています。

4. get()

それでは、 get()操作のマイクロベンチマークを追加しましょう。

4.1. ベンチマークテスト

まず、 ArrayList’ s get()操作の場合:

@Benchmark
public int getArrayList() {
    return arrayList.get(getValue);
}

同様に、Troveの TIntArrayList には、次のものがあります。

@Benchmark
public int getTroveIntList() {
    return tList.get(getValue);
}

また、Coltの cern.colt.list.IntArrayListの場合、 get()メソッドは次のようになります。

@Benchmark
public int getColtIntList() {
    return coltList.get(getValue);
}

最後に、Fastutilの IntArrayList について、 getInt()操作をテストします。

@Benchmark
public int getFastUtilIntList() {
    return fastUtilList.getInt(getValue);
}

4.2. 試験結果

その後、ベンチマークを実行して結果を確認します。

Benchmark           Mode  Cnt  Score   Error  Units
getArrayList        ss     20  5.539 ± 0.552  ms/op
getColtIntList      ss     20  4.598 ± 0.825  ms/op
getFastUtilIntList  ss     20  4.585 ± 0.489  ms/op
getTroveIntList     ss     20  4.715 ± 0.751  ms/op

スコアの差はそれほど大きくありませんが、 getArrayList()の動作が遅いことがわかります。

残りのライブラリについては、ほぼ同じ get()メソッドの実装があります。 それ以上の作業なしでint[]からすぐに値を取得します。そのため、Colt、Fastutil、およびTroveは get()操作で同様のパフォーマンスを示します。

5. contains()

最後に、リストのタイプごとに contains()メソッドをテストしてみましょう。

5.1. ベンチマークテスト

ArrayList’ s contains()メソッドの最初のマイクロベンチマークを追加しましょう。

@Benchmark
public boolean containsArrayList() {
    return arrayList.contains(getValue);
}

同様に、Troveの TIntArrayList の場合、 contains()ベンチマークは次のようになります。

@Benchmark
public boolean containsTroveIntList() {
    return tList.contains(getValue);
}

同様に、Coltの cern.colt.list.IntArrayList.contains()のテストは次のとおりです。

@Benchmark
public boolean containsColtIntList() {
    return coltList.contains(getValue);
}

また、Fastutilの IntArrayListの場合、 contains()メソッドのテストは次のようになります。

@Benchmark
public boolean containsFastUtilIntList() {
    return fastUtilList.contains(getValue);
}

5.2. 試験結果

最後に、テストを実行して結果を比較します。

Benchmark                  Mode  Cnt   Score    Error  Units
containsArrayList          ss     20   2.083  ± 1.585  ms/op
containsColtIntList        ss     20   1.623  ± 0.960  ms/op
containsFastUtilIntList    ss     20   1.406  ± 0.400  ms/op
containsTroveIntList       ss     20   1.512  ± 0.307  ms/op

いつものように、containsArrayListメソッドのパフォーマンスは最低です。 対照的に、Trove、Colt、およびFastutilは、Javaのコアソリューションと比較してパフォーマンスが優れています。

今回は、どのライブラリを選択するかは開発者次第です。 3つのライブラリすべての結果は、それらが同一であると見なすのに十分に近いものです。

6. 結論

この記事では、JVMベンチマークテストを通じてプリミティブリストの実際の実行時パフォーマンスを調査しました。 さらに、テスト結果をJDKのArrayListと比較しました。

また、ここに示す数値は単なるJMHベンチマーク結果であることに注意してください。常に、特定のシステムとランタイムの範囲でテストしてください。

いつものように、この記事の完全なコードは、GitHubから入手できます。