web-dev-qa-db-ja.com

.toArray(new MyClass [0])または.toArray(new MyClass [myList.size()])?

ArrayListがあると仮定します

ArrayList<MyClass> myList;

ToArrayを呼び出したいのですが、使用するパフォーマンス上の理由はありますか

MyClass[] arr = myList.toArray(new MyClass[myList.size()]);

以上

MyClass[] arr = myList.toArray(new MyClass[0]);

冗長性が少ないため、2番目のスタイルを好みます。また、空の配列が実際に作成されないことをコンパイラが確認すると想定しましたが、それが本当かどうか疑問に思っていました。

もちろん、99%のケースで違いはありませんが、通常のコードと最適化された内部ループの間で一貫したスタイルを維持したいと思います...

136
itsadok

直感に反して、Hotspot 8の最速バージョンは次のとおりです。

MyClass[] arr = myList.toArray(new MyClass[0]);

Jmhを使用してマイクロベンチマークを実行しました。結果とコードは以下のとおりです。空の配列を使用したバージョンは、サイズ変更済みの配列を使用したバージョンよりも常に優れていることがわかります。正しいサイズの既存の配列を再利用できる場合、結果が異なる可能性があることに注意してください。

ベンチマーク結果(マイクロ秒単位のスコア、小さい=より良い):

Benchmark                      (n)  Mode  Samples    Score   Error  Units
c.a.p.SO29378922.preSize         1  avgt       30    0.025 ▒ 0.001  us/op
c.a.p.SO29378922.preSize       100  avgt       30    0.155 ▒ 0.004  us/op
c.a.p.SO29378922.preSize      1000  avgt       30    1.512 ▒ 0.031  us/op
c.a.p.SO29378922.preSize      5000  avgt       30    6.884 ▒ 0.130  us/op
c.a.p.SO29378922.preSize     10000  avgt       30   13.147 ▒ 0.199  us/op
c.a.p.SO29378922.preSize    100000  avgt       30  159.977 ▒ 5.292  us/op
c.a.p.SO29378922.resize          1  avgt       30    0.019 ▒ 0.000  us/op
c.a.p.SO29378922.resize        100  avgt       30    0.133 ▒ 0.003  us/op
c.a.p.SO29378922.resize       1000  avgt       30    1.075 ▒ 0.022  us/op
c.a.p.SO29378922.resize       5000  avgt       30    5.318 ▒ 0.121  us/op
c.a.p.SO29378922.resize      10000  avgt       30   10.652 ▒ 0.227  us/op
c.a.p.SO29378922.resize     100000  avgt       30  139.692 ▒ 8.957  us/op

参考のために、コード:

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
public class SO29378922 {
  @Param({"1", "100", "1000", "5000", "10000", "100000"}) int n;
  private final List<Integer> list = new ArrayList<>();
  @Setup public void populateList() {
    for (int i = 0; i < n; i++) list.add(0);
  }
  @Benchmark public Integer[] preSize() {
    return list.toArray(new Integer[n]);
  }
  @Benchmark public Integer[] resize() {
    return list.toArray(new Integer[0]);
  }
}

ブログ記事 古代の知恵の配列 で同様の結果、完全な分析、議論を見つけることができます。要約すると、JVMおよびJITコンパイラには、適切なサイズの新しい配列を安価に作成および初期化できるいくつかの最適化が含まれており、自分で配列を作成する場合、これらの最適化は使用できません。

78
assylias

ArrayList in Java 5 の時点で、適切なサイズ(またはそれ以上)の場合、配列はすでに満たされています。

MyClass[] arr = myList.toArray(new MyClass[myList.size()]);

1つの配列オブジェクトを作成し、入力して「arr」に返します。一方

MyClass[] arr = myList.toArray(new MyClass[0]);

2つの配列を作成します。 2番目は、長さが0のMyClassの配列です。したがって、すぐに破棄されるオブジェクトのオブジェクトが作成されます。ソースコードが示唆する限り、コンパイラ/ JITはこの1つを最適化できないため、作成されません。また、長さゼロのオブジェクトを使用すると、toArray()-メソッド内でキャストされます。

ArrayList.toArray()のソースを参照してください。

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

最初の方法を使用して、1つのオブジェクトのみを作成し、(暗黙的ではあるが高価な)キャストを回避します。

119
Georgi

JetBrains Intellij Ideaインスペクションから:

コレクションを配列に変換するには、2つのスタイルがあります。 c.toArray(new String [c.size()]))または空の配列(たとえば c.toArray(新しい文字列[0])

適切なサイズの配列を作成するために必要なリフレクション呼び出しは非常に遅かったため、古いJava事前サイズの配列を使用するバージョンが推奨されていました。また、空の配列バージョンのパフォーマンスは、事前にサイズ設定されたバージョンと同じで、場合によってはさらに向上します。また、事前にサイズ設定された配列を渡すことは、 サイズ そして toArray 操作中にコレクションが同時に縮小された場合、配列の最後に余分なnullが発生する可能性のある呼び出し。

この検査により、均一なスタイルに従うことができます。空の配列(最新のJavaで推奨)を使用するか、事前にサイズ設定された配列(古いJavaバージョンまたはHotSpotベースのJVM)。

この場合、最新のJVMは反射配列の構築を最適化するため、パフォーマンスの違いはわずかです。このようなボイラープレートコードでコレクションに2回名前を付けることは素晴らしい考えではないため、最初の方法は避けたいと思います。 2番目のもう1つの利点は、同期された同時コレクションで動作することです。最適化を行いたい場合は、空の配列を再利用するか(空の配列は不変で共有できます)、またはプロファイラー(!)を使用します。

14

前者の方が効率的です。

これは、2番目のケースでは次のとおりです。

MyClass[] arr = myList.toArray(new MyClass[0]);

ランタイムは空の配列(サイズ0)を実際に作成し、toArrayメソッド内で実際のデータに合わせて別の配列を作成します。この作成は、次のコード(jdk1.5.0_10から取得)を使用してリフレクションを使用して行われます。

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        a = (T[])Java.lang.reflect.Array.
    newInstance(a.getClass().getComponentType(), size);
System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

最初の形式を使用することにより、2番目の配列の作成を回避し、リフレクションコードも回避します。

4

toArrayは、渡された配列が正しいサイズ(つまり、リストの要素に適合するのに十分な大きさ)であることを確認し、そうであればそれを使用します。したがって、配列のサイズが必要なサイズよりも小さい場合、新しい配列が再帰的に作成されます。

あなたの場合、サイズがゼロの配列は不変であるため、静的な最終変数に安全に昇格させることができます。これにより、コードが少しきれいになり、呼び出しごとに配列が作成されなくなります。とにかくメソッド内に新しい配列が作成されるので、読みやすく最適化されています。

おそらく、より速いバージョンは正しいサイズの配列を渡すことですが、proveこのコードがパフォーマンスのボトルネックにならない限り、他の方法で証明されるまで、ランタイムパフォーマンスより読みやすさを優先してください。

3
Dave Cheney