Ist es besser, System.arraycopy (…) als eine for-Schleife zum Kopieren von Arrays zu verwenden?

89

Ich möchte ein neues Array von Objekten erstellen, die zwei kleinere Arrays zusammensetzen.

Sie können nicht null sein, aber die Größe kann 0 sein.

Ich kann nicht zwischen diesen beiden Möglichkeiten wählen: Sind sie gleichwertig oder ist eine effizienter (zum Beispiel system.arraycopy () kopiert ganze Blöcke)?

MyObject[] things = new MyObject[publicThings.length+privateThings.length];
System.arraycopy(publicThings, 0, things, 0, publicThings.length);
System.arraycopy(privateThings, 0, things,  publicThings.length, privateThings.length);

oder

MyObject[] things = new MyObject[publicThings.length+privateThings.length];
for (int i = 0; i < things.length; i++) {
    if (i<publicThings.length){
        things[i] = publicThings[i]
    } else {
        things[i] = privateThings[i-publicThings.length]        
    }
}

Ist der einzige Unterschied das Aussehen des Codes?

EDIT: Danke für die verknüpfte Frage, aber sie scheinen eine ungelöste Diskussion zu haben:

Ist es wirklich schneller, wenn it is not for native types: Byte [], Objekt [], Zeichen []? in allen anderen Fällen wird eine Typprüfung durchgeführt, was mein Fall wäre und daher gleichwertig wäre ... nein?

Bei einer anderen verknüpften Frage heißt es, dass the size matters a lotbei einer Größe von> 24 system.arraycopy () für weniger als 10 das Handbuch für die Schleife besser ist ...

Jetzt bin ich wirklich verwirrt.

Daren
quelle
16
arraycopy()ist ein nativer Anruf, der mit Sicherheit schneller ist.
Sotirios Delimanolis
4
Haben Sie versucht, die beiden verschiedenen Implementierungen zu vergleichen?
Alex
5
Werfen
Sotirios Delimanolis
15
Sie sollten auswählen, was für Sie am besten lesbar und in Zukunft am einfachsten zu warten ist. Nur wenn Sie festgestellt haben, dass dies die Ursache für einen Engpass ist, sollten Sie Ihren Ansatz ändern.
Arshajii
1
Das Rad nicht neu erfinden!
Camickr

Antworten:

87
public void testHardCopyBytes()
{
    byte[] bytes = new byte[0x5000000]; /*~83mb buffer*/
    byte[] out = new byte[bytes.length];
    for(int i = 0; i < out.length; i++)
    {
        out[i] = bytes[i];
    }
}

public void testArrayCopyBytes()
{
    byte[] bytes = new byte[0x5000000]; /*~83mb buffer*/
    byte[] out = new byte[bytes.length];
    System.arraycopy(bytes, 0, out, 0, out.length);
}

Ich weiß, dass JUnit-Tests nicht wirklich die besten für das Benchmarking sind, aber
testHardCopyBytes dauerte 0,157 Sekunden
und
testArrayCopyBytes 0,086 Sekunden.

Ich denke, es hängt von der virtuellen Maschine ab, aber es sieht so aus, als würde sie Speicherblöcke kopieren, anstatt einzelne Array-Elemente zu kopieren. Dies würde die Leistung absolut steigern.

EDIT:
Es sieht so aus, als ob die Leistung von System.arraycopy allgegenwärtig ist. Wenn Strings anstelle von Bytes verwendet werden und Arrays klein sind (Größe 10), erhalte ich folgende Ergebnisse:

    String HC:  60306 ns
    String AC:  4812 ns
    byte HC:    4490 ns
    byte AC:    9945 ns

So sieht es aus, wenn Arrays die Größe 0x1000000 haben. Es sieht so aus, als ob System.arraycopy mit größeren Arrays definitiv gewinnt.

    Strs HC:  51730575 ns
    Strs AC:  24033154 ns
    Bytes HC: 28521827 ns
    Bytes AC: 5264961 ns

Wie eigenartig!

Vielen Dank, Daren, dass Sie darauf hingewiesen haben, dass Referenzen anders kopiert werden. Das machte das Problem viel interessanter!

Trent klein
quelle
2
Vielen Dank für Ihre Mühe, aber Sie haben scheinbar entscheidende Punkte übersehen: Nicht native Typen (erstellen Sie eine zufällige Klasse mit einem beliebigen Wert, damit das Array Referenzen enthält) und Größe ... scheinen für kleinere Array-Größen die manuelle for-Schleife schneller zu sein. Möchtest du das korrigieren?
Daren
2
Oh wow, du hast recht! Das ist interessant. Das Platzieren von Strings in diesen Arrays anstelle von Bytes macht einen großen Unterschied: <<< testHardCopyStrs: 0.161s >>> <<< testArrayCopyStrs: 0.170s >>>
Trent Small
Welche Ergebnisse erzielen Sie dann? Interessant wäre auch, es mit Arraygröße = 10 zu versuchen ... danke! (Ich wünschte ich hätte meine IDE hier, ich codiere ohne Compiler).
Daren
Nun habe ich sie in einige System.nanoTime () -Aufrufe eingeschlossen und Größe = 10 festgelegt, um zu sehen, wie viele Nanosekunden jeweils benötigt werden. Sieht aus wie für kleine primitive Arrays, Schleifen sind besser; Für Referenzen ist arrayCopy besser: <<< testHardCopyBytes: 4491 ns >>> <<< testHardCopyStrs: 56778 ns >>> <<< testArrayCopyBytes: 10265 ns >>> <<< testArrayCopyStrs: 4490 ns >>>
Trent Kleiner
Sehr interessante Ergebnisse! ich danke dir sehr! Können Sie bitte Ihre Antwort so bearbeiten, dass sie dies enthält, und ich werde sie gerne akzeptieren, damit sie für alle als erste sichtbar bleibt ... Sie haben bereits meine Stimme abgegeben. :)
Daren
36

Arrays.copyOf(T[], int)ist leichter zu lesen. Intern System.arraycopy()wird ein nativer Anruf verwendet.

Schneller geht es nicht!

Philipp Sander
quelle
Es scheint, dass Sie sich auf einige Dinge verlassen können, aber danke, dass Sie auf diese Funktion hingewiesen haben, die ich nicht kannte und die in der Tat leichter zu lesen ist. :)
Daren
Ja! es hängt von einigen Dingen ab, wie @Svetoslav Tsolov sagte. Ich wollte nur auf Arrays.copyOf
Philipp Sander
2
copyOfkann nicht immer ersetzen arraycopy, ist aber für diesen Anwendungsfall geeignet.
Blaisorblade
1
NB Wenn Sie sich die Leistung ansehen, ist dies nicht so schnell wie System.arraycopy (), da eine Speicherzuweisung erforderlich ist. Wenn sich dies in einer Schleife befindet, führen wiederholte Zuweisungen zu einer Speicherbereinigung, die einen massiven Leistungseinbruch darstellt.
Will Calderwood
1
@PhilippSander Nur um zu überprüfen, ob ich nicht dumm war, habe ich Code zum Kopieren eines 1-MB-Arrays in meiner Spieleschleife hinzugefügt, wodurch GC fast nie ausgelöst wird. Mit Array.copyOf () rief mein DVM 5 Mal pro Sekunde GC auf und das Spiel wurde extrem verzögert. Ich denke, man kann mit Sicherheit sagen, dass eine Speicherzuweisung stattfindet.
Will Calderwood
17

Dies hängt von der virtuellen Maschine ab, aber System.arraycopy sollte Ihnen die bestmögliche Leistung bieten.

Ich habe 2 Jahre als Java-Entwickler für eingebettete Systeme gearbeitet (wo Leistung eine große Priorität hat) und überall dort, wo System.arraycopy verwendet werden konnte, habe ich es meistens in vorhandenem Code verwendet / gesehen. Es wird immer Schleifen vorgezogen, wenn die Leistung ein Problem darstellt. Wenn die Leistung kein großes Problem darstellt, würde ich mich jedoch für die Schleife entscheiden. Viel einfacher zu lesen.


quelle
Dinge wie Arraygröße und -typ (einfach oder geerbt) scheinen die Leistung zu beeinflussen.
Daren
2
Ja, es ist an sich keine "native Leistung", weshalb ich sagte, ich benutze sie "meistens", wo ich kann (Sie werden feststellen, dass sie meistens das Kopieren von Schleifen gewinnt). Ich denke, der Grund ist: Wenn es sich um ein kleines Array eines primitiven Typs handelt, ist der "Cost-to-Call" größer als der Leistungsschub. Die Verwendung von JNI kann die Leistung aus demselben Grund beeinträchtigen - der native Code selbst ist schnell, ruft ihn jedoch aus einem Java-Prozess auf - nicht so sehr.
Kleinere Korrekturen, Arrays.copy ist kein JNI, sondern eine intrinsische. Intrinsics sind viel schneller als JNI. Wie und wann der JIT-Compiler daraus einen intrinsischen Compiler macht, hängt von der verwendeten JVM / dem verwendeten Compiler ab.
Nitsan Wakart
1
Arrays.copyexistiert nicht, Arrays.copyOfist eine Bibliotheksfunktion.
Blaisorblade
12

Anstatt mich auf Spekulationen und möglicherweise veraltete Informationen zu verlassen, habe ich einige Benchmarks verwendet . In der Tat kommt Caliper mit einigen Beispielen, einschließlich eines CopyArrayBenchmark, das genau diese Frage misst! Alles was Sie tun müssen, ist zu rennen

mvn exec:java -Dexec.mainClass=com.google.caliper.runner.CaliperMain -Dexec.args=examples.CopyArrayBenchmark

Meine Ergebnisse basieren auf der 64-Bit-Server-VM Java HotSpot (TM) von Oracle, 1.8.0_31-b13, die Mitte 2010 auf einem MacBook Pro (macOS 10.11.6 mit Intel Arrandale i7, 8 GiB RAM) ausgeführt wird. Ich glaube nicht, dass es nützlich ist, die rohen Timing-Daten zu veröffentlichen. Vielmehr werde ich die Schlussfolgerungen mit den unterstützenden Visualisierungen zusammenfassen.

Zusammenfassend:

  • Das Schreiben einer manuellen forSchleife zum Kopieren jedes Elements in ein neu instanziiertes Array ist niemals vorteilhaft, sei es für kurze oder lange Arrays.
  • Arrays.copyOf(array, array.length)und array.clone()sind beide konstant schnell. Diese beiden Techniken sind in der Leistung nahezu identisch. Welches Sie wählen, ist Geschmackssache.
  • System.arraycopy(src, 0, dest, 0, src.length)ist fast so schnell wie und , aber nicht ganz konsequent. (Siehe den Fall für 50000 s.) Aus diesem Grund und aufgrund der Ausführlichkeit des Aufrufs würde ich empfehlen, wenn Sie eine genaue Kontrolle darüber benötigen, welche Elemente wohin kopiert werden.Arrays.copyOf(array, array.length)array.clone()intSystem.arraycopy()

Hier sind die Zeitdiagramme:

Timings zum Kopieren von Arrays der Länge 5 Timings zum Kopieren von Arrays mit einer Länge von 500 Timings zum Kopieren von Arrays mit einer Länge von 50000

200_Erfolg
quelle
2
Hat das int-Kopieren etwas Seltsames? Es scheint seltsam, dass die Arraykopie bei Ints in großem Maßstab langsam ist.
Walberg
6

Das Ausführen nativer Methoden wie hat Arrays.copyOf(T[], int)zwar einen gewissen Overhead, bedeutet jedoch nicht, dass es nicht schnell ist, da Sie es mit JNI ausführen.

Am einfachsten ist es, einen Benchmark zu schreiben und zu testen.

Sie können überprüfen, ob dies Arrays.copyOf(T[], int)schneller als Ihre normale forSchleife ist.

Der Benchmark-Code von hier : -

public void test(int copySize, int copyCount, int testRep) {
    System.out.println("Copy size = " + copySize);
    System.out.println("Copy count = " + copyCount);
    System.out.println();
    for (int i = testRep; i > 0; --i) {
        copy(copySize, copyCount);
        loop(copySize, copyCount);
    }
    System.out.println();
}

public void copy(int copySize, int copyCount) {
    int[] src = newSrc(copySize + 1);
    int[] dst = new int[copySize + 1];
    long begin = System.nanoTime();
    for (int count = copyCount; count > 0; --count) {
        System.arraycopy(src, 1, dst, 0, copySize);
        dst[copySize] = src[copySize] + 1;
        System.arraycopy(dst, 0, src, 0, copySize);
        src[copySize] = dst[copySize];
    }
    long end = System.nanoTime();
    System.out.println("Arraycopy: " + (end - begin) / 1e9 + " s");
}

public void loop(int copySize, int copyCount) {
    int[] src = newSrc(copySize + 1);
    int[] dst = new int[copySize + 1];
    long begin = System.nanoTime();
    for (int count = copyCount; count > 0; --count) {
        for (int i = copySize - 1; i >= 0; --i) {
            dst[i] = src[i + 1];
        }
        dst[copySize] = src[copySize] + 1;
        for (int i = copySize - 1; i >= 0; --i) {
            src[i] = dst[i];
        }
        src[copySize] = dst[copySize];
    }
    long end = System.nanoTime();
    System.out.println("Man. loop: " + (end - begin) / 1e9 + " s");
}

public int[] newSrc(int arraySize) {
    int[] src = new int[arraySize];
    for (int i = arraySize - 1; i >= 0; --i) {
        src[i] = i;
    }
    return src;
}

System.arraycopy()verwendet JNI (Java Native Interface) zum Kopieren eines Arrays (oder von Teilen davon), so dass es unglaublich schnell ist, wie Sie hier bestätigen können

Rahul Tripathi
quelle
Dieser Code verwendet int [], können Sie es mit String [] versuchen (initialisiert mit verschiedenen Werten: "1", "2" usw., da sie unveränderlich sind
Daren
1
JNI ist sehr langsam . System.arraycopybenutzt es nicht.
Chai T. Rex
Nein, System.arraycopyverwendet kein JNI, das nur zum Aufrufen von Bibliotheken von Drittanbietern dient. Stattdessen handelt es sich um einen nativen Aufruf. Dies bedeutet, dass in der VM eine native Implementierung vorhanden ist.
Spheenik
6

Es ist nicht möglich, dass dies Arrays.copyOfschneller ist als System.arraycopyda dies die Implementierung von copyOf:

public static int[] copyOf(int[] original, int newLength) {
    int[] copy = new int[newLength];
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}
DimaD
quelle
4

System.arraycopy()ist ein nativer Aufruf, der Kopiervorgänge direkt im Speicher ausführt. Eine einzelne Speicherkopie wäre immer schneller als Ihre for-Schleife

Nandkumar Tekale
quelle
3
Ich habe gelesen, dass es für nicht native Typen (jede erstellte Klasse, wie meine) möglicherweise nicht so effizient ist ... und für kleine Größen (in meinem Fall) kann das Handbuch für die Schleife besser sein ... Möchten Sie einen Kommentar abgeben?
Daren
In der Tat hat System.arraycopy () einen gewissen Overhead, so dass für kleine Arrays (n = ~ 10) eine Schleife tatsächlich schneller ist
RecursiveExceptionException