it-swarm.com.de

.toArray (neue MyClass [0]) oder .toArray (neue MyClass [myList.size ()])?

Angenommen, ich habe eine ArrayList

ArrayList<MyClass> myList;

Und ich möchte bei Array anrufen, gibt es einen Leistungsgrund zu nutzen

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

über

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

?

Ich bevorzuge den zweiten Stil, da er weniger ausführlich ist und ich davon ausgegangen bin, dass der Compiler sicherstellen wird, dass das leere Array nicht wirklich erstellt wird, aber ich habe mich gefragt, ob das stimmt.

Natürlich macht es in 99% der Fälle keinen Unterschied, aber ich möchte einen konsistenten Stil zwischen meinem normalen Code und meinen optimierten inneren Schleifen beibehalten ...

136
itsadok

Die schnellste Version von Hotspot 8 ist:

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

Ich habe einen Micro-Benchmark mit jmh durchgeführt. Die Ergebnisse und der Code unten zeigen, dass die Version mit einem leeren Array die Version mit einem vorgegebenen Array durchweg übertrifft. Beachten Sie, dass das Ergebnis möglicherweise anders ausfällt, wenn Sie ein vorhandenes Array der richtigen Größe wiederverwenden können.

Benchmark-Ergebnisse (Score in Mikrosekunden, kleiner = besser):

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

Als Referenz dient der Code:

@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]);
  }
}

Ähnliche Ergebnisse, vollständige Analysen und Diskussionen finden Sie im Blog-Beitrag Arrays of Wisdom of the Ancients . Zusammenfassend lässt sich sagen, dass der JVM- und JIT-Compiler mehrere Optimierungen enthält, mit denen ein neues Array mit der richtigen Größe kostengünstig erstellt und initialisiert werden kann. Diese Optimierungen können nicht verwendet werden, wenn Sie das Array selbst erstellen.

78
assylias

Ab ArrayList in Java 5 wird das Array bereits gefüllt, wenn es die richtige Größe hat (oder größer ist)

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

erstellt ein Array-Objekt, füllt es und gibt es an "arr" zurück. Auf der anderen Seite

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

erstellt zwei Arrays. Das zweite ist ein Array von MyClass mit der Länge 0. Es gibt also eine Objekterstellung für ein Objekt, das sofort weggeworfen wird. Soweit der Quellcode vermuten lässt, kann der Compiler/JIT diesen nicht so optimieren, dass er nicht erstellt wird. Darüber hinaus führt die Verwendung des Objekts mit der Länge Null zu Casting (s) innerhalb der toArray () - Methode.

Siehe die Quelle von 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;
}

Verwenden Sie die erste Methode, damit nur ein Objekt erstellt wird, und vermeiden Sie (implizite, aber dennoch teure) Castings.

119
Georgi

Aus der Inspektion von JetBrains Intellij Idea:

Es gibt zwei Stile, um eine Sammlung in ein Array zu konvertieren: entweder mit einem Array in vorgefertigter Größe (wie c.toArray (neuer String [c.size ()])) oder mit einem leeren Array (wie c.toArray (neuer String [0]).

In älteren Java) Versionen wurde die Verwendung eines Arrays in vorgefertigter Größe empfohlen, da der Reflection-Aufruf, der zum Erstellen eines Arrays in der richtigen Größe erforderlich ist, ziemlich langsam war. Da OpenJDK 6 jedoch zu spät aktualisiert wurde, war dieser Aufruf in sich abgeschlossen Dadurch wird die Leistung der leeren Array-Version im Vergleich zur vorformatierten Version gleich und manchmal sogar noch besser. Das Weitergeben eines vorformatierten Arrays ist für eine gleichzeitige oder synchronisierte Erfassung gefährlich, da zwischen den beiden Arrays ein Datenrennen möglich ist größe und toArray Aufruf, der zu zusätzlichen Nullen am Ende des Arrays führen kann, wenn die Auflistung während des Vorgangs gleichzeitig verkleinert wurde.

Diese Inspektion ermöglicht es, dem einheitlichen Stil zu folgen: entweder mit einem leeren Array (was in modernem Java empfohlen wird) oder mit einem Array in vorgefertigter Größe (was in älteren Java -Versionen oder Nicht-Java) schneller sein kann. HotSpot-basierte JVMs).

Moderne JVMs optimieren in diesem Fall den Aufbau von reflektierenden Arrays, sodass der Leistungsunterschied gering ist. Es ist keine gute Idee, die Sammlung in einem solchen Code zweimal zu benennen, daher würde ich die erste Methode vermeiden. Ein weiterer Vorteil des zweiten ist, dass es mit synchronisierten und gleichzeitigen Sammlungen funktioniert. Wenn Sie eine Optimierung vornehmen möchten, verwenden Sie das leere Array erneut (leere Arrays sind unveränderlich und können gemeinsam genutzt werden) oder verwenden Sie einen Profiler (!).

14

Der erste Fall ist effizienter.

Das liegt daran, dass im zweiten Fall:

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

die Laufzeit erstellt tatsächlich ein leeres Array (mit der Größe Null) und erstellt dann innerhalb der toArray-Methode ein anderes Array, das den tatsächlichen Daten entspricht. Diese Erstellung erfolgt unter Verwendung von Reflection mit dem folgenden Code (aus 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;
}

Durch die Verwendung des ersten Formulars vermeiden Sie die Erstellung eines zweiten Arrays und auch den Reflektionscode.

4

toArray überprüft, ob das übergebene Array die richtige Größe hat (dh groß genug, um die Elemente aus Ihrer Liste aufzunehmen), und verwendet diese Größe, wenn dies der Fall ist. Wenn die Größe des Arrays kleiner als erforderlich ist, wird folglich reflexartig ein neues Array erstellt.

In Ihrem Fall ist ein Array der Größe Null unveränderlich und kann daher sicher zu einer statischen Endvariablen erhöht werden, wodurch der Code möglicherweise ein wenig übersichtlicher wird, sodass das Array nicht bei jedem Aufruf erstellt wird. In der Methode wird trotzdem ein neues Array erstellt, um die Lesbarkeit zu optimieren.

Die schnellere Version besteht wahrscheinlich darin, das Array mit der korrekten Größe zu übergeben. Wenn Sie jedoch nicht beweisen diesen Code als Leistungsengpass bezeichnen, ziehen Sie die Lesbarkeit der Laufzeitleistung vor, bis das Gegenteil bewiesen ist.

3
Dave Cheney