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


Ich möchte ein neues Array von Objekten erstellen, indem ich zwei kleinere Arrays zusammenstelle.

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 äquivalent oder ist es effizienter (zum Beispiel system.arraycopy () kopiert ganze Stü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?

BEARBEITEN: 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 [], char []? in allen anderen Fällen wird eine Typprüfung durchgeführt, was mein Fall wäre und das wäre äquivalent ... nein?

Bei einer anderen verbundenen Frage sagen sie das the size matters a lot, für die Größe> 24 system.arraycopy () gewinnt, für kleiner als 10, manuelle Schleife ist besser ...

Jetzt bin ich wirklich verwirrt.


76
2017-09-05 14:15


Ursprung


Antworten:


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ß, JUnit-Tests sind nicht wirklich die besten für Benchmarking, aber
testHardCopyBytes benötigte 0.157 Sekunden
und
testArrayCopyBytes benötigte 0,086 Sekunden, um abzuschließen.

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

BEARBEITEN:
Es sieht so aus, als wäre die Leistung von System.arraycopy überall. Wenn Strings anstelle von Bytes verwendet werden und Arrays klein sind (Größe 10), Ich bekomme diese 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 scheint, dass System.arraycopy definitiv mit größeren Arrays gewinnt.

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

Wie eigenartig!

Danke, Daren, dass Sie darauf hingewiesen haben, dass Referenzen anders sind. Es machte das ein viel interessanteres Problem!


70
2017-09-05 14:28



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

Sie können es nicht schneller bekommen!


30
2017-09-05 14:19



Es hängt von der virtuellen Maschine ab, aber System.arraycopy sollte Ihnen den größtmöglichen Nutzen für die native Leistung bieten.

Ich habe 2 Jahre als Java-Entwickler für Embedded-Systeme gearbeitet (wo Leistung eine große Priorität hat) und überall, wo System.arraycopy verwendet werden kann, habe ich es meistens benutzt / gesehen, wie es in existierendem Code verwendet wurde. Overloops werden immer bevorzugt, wenn Leistung ein Problem darstellt. Wenn Leistung kein großes Problem ist, würde ich mit der Schleife gehen. Viel einfacher zu lesen.


13
2017-09-05 14:18



Ausführen nativer Methoden wie Arrays.copyOf(T[], int) hat einige Overhead, aber es bedeutet nicht, dass es nicht schnell ist, wie Sie es mit JNI ausführen.

Der einfachste Weg ist, einen Benchmark zu schreiben und zu testen.

Das kannst du überprüfen Arrays.copyOf(T[], int) ist schneller als normal for Schleife.

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), um ein Array (oder Teile davon) zu kopieren, also ist es blitzschnell, wie Sie bestätigen können Hier


6
2017-09-05 14:27



Anstatt sich auf Spekulationen und möglicherweise veraltete Informationen zu verlassen, habe ich einige Benchmarks mit verwendet . In der Tat kommt Caliper mit einigen Beispielen, einschließlich a CopyArrayBenchmark das misst genau diese Frage! 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 Oracle Java HotSpot 64-Bit Server-VM 1.8.0_31-b13, die auf einem MacBook Pro Mitte 2010 läuft (macOS 10.11.6 mit Intel Arrandale i7, 8 GiB RAM). Ich glaube nicht, dass es nützlich ist, die rohen Timing-Daten zu posten. Vielmehr fasse ich die Schlussfolgerungen mit den unterstützenden Visualisierungen zusammen.

Zusammenfassend:

  • Ein Handbuch schreiben for Eine Schleife, um jedes Element in ein neu instantiiertes Array zu kopieren, ist nie vorteilhaft, weder für kurze Arrays noch für lange Arrays.
  • Arrays.copyOf(Array, Array.length) und Array.clone() sind beide konstant schnell. Diese zwei Techniken sind in der Leistung fast identisch; welches du wählst, ist eine Frage des Geschmacks.
  • System.arraycopy(src, 0, dest, 0, src.length) ist fast so schnell wie Arrays.copyOf(Array, Array.length) und Array.clone(), aber nicht ganz so konsequent. (Siehe den Fall für 50000 ints.) Deshalb und die Ausführlichkeit des Anrufs würde ich empfehlen System.arraycopy() wenn Sie eine genaue Kontrolle darüber benötigen, welche Elemente wo kopiert werden.

Hier sind die Timing Plots:

Timings for copying arrays of length 5 Timings for copying arrays of length 500 Timings for copying arrays of length 50000


4
2018-03-14 04:36



Wie ist das möglich? Arrays.copyOf ist dann schneller System.arraycopy Wenn dies die Implementierung von copyOf ist:

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;
}

4
2018-05-10 11:37



System.arraycopy() ist ein systemeigener Aufruf, der die Operation direkt in den Speicher kopiert. Eine einzelne Speicherkopie wäre immer schneller als Ihre for-Schleife


3
2017-09-05 14:21