Warum ist System.arraycopy in Java nativ?

83

Ich war überrascht, in der Java-Quelle zu sehen, dass System.arraycopy eine native Methode ist.

Der Grund ist natürlich, dass es schneller ist. Aber welche nativen Tricks kann der Code anwenden, um ihn schneller zu machen?

Warum nicht einfach das ursprüngliche Array durchlaufen und jeden Zeiger auf das neue Array kopieren - das ist doch nicht so langsam und umständlich?

James B.
quelle

Antworten:

80

Im nativen Code kann dies mit einem einzelnen memcpy/ erfolgen memmove, im Gegensatz zu n verschiedenen Kopiervorgängen. Der Leistungsunterschied ist erheblich.

Péter Török
quelle
@Peter, also können Sie innerhalb des nativen Codes mit dem Java-Speichermodell arbeiten? (Ich hatte noch nie einen Grund, einen einheimischen Malarkey zu machen)
James B
8
Tatsächlich konnten nur einige Unterfälle von arraycopymit memcpy/ implementiert werden memmove. Andere erfordern eine Laufzeit-Typprüfung für jedes kopierte Element.
Stephen C
1
@ Stephen C, interessant - warum ist das so?
Péter Török
3
@ Péter Török - Kopieren Sie von einem Object[]mit StringObjekten besetzten Objekt nach a String[]. Siehe letzten Absatz von java.sun.com/javase/6/docs/api/java/lang/…
Stephen C
3
Peter, Object [] und Byte [] + char [] werden am häufigsten kopiert, keiner von ihnen erfordert eine explizite Typprüfung. Der Compiler ist intelligent genug, um NICHT zu prüfen, es sei denn, dies wird benötigt, und in 99,9% der Fälle ist dies praktisch nicht der Fall. Der lustige Teil ist, dass die kleinen Kopien (weniger als eine Cache-Zeile) ziemlich dominant sind, daher ist "memcpy" für kleine Sachen, die schnell sind, wirklich wichtig.
Bests
15

Es kann nicht in Java geschrieben werden. Native Code kann den Unterschied zwischen Arrays von Object und Arrays von Primitiven ignorieren oder beseitigen. Java kann das nicht, zumindest nicht effizient.

Und es kannmemcpy() aufgrund der Semantik, die überlappende Arrays erfordern, nicht mit einem einzigen geschrieben werden .

Marquis von Lorne
quelle
5
Gut, also memmovedann. Obwohl ich nicht denke, dass es im Kontext dieser Frage einen großen Unterschied macht.
Péter Török
Auch nicht memmove (), siehe die Kommentare von @Stephen C zu einer anderen Antwort.
Marquis von Lorne
Hab das schon gesehen, da das zufällig meine eigene Antwort war ;-) Aber trotzdem danke.
Péter Török
1
@ Geek Arrays, die sich überlappen. Wenn die Quell- und Ziel-Arrays und die gleichen und nur die Offsets unterschiedlich sind, wird das Verhalten sorgfältig angegeben und memcpy () entspricht nicht.
Marquis von Lorne
1
Es kann nicht in Java geschrieben werden? Könnte man nicht eine generische Methode schreiben, um Unterklassen von Object zu behandeln, und dann eine für jeden der primitiven Typen?
Michael Dorst
10

Dies hängt natürlich von der Implementierung ab.

HotSpot behandelt es als "intrinsisch" und fügt Code an der Anrufstelle ein. Das ist Maschinencode, kein langsamer alter C-Code. Dies bedeutet auch, dass die Probleme mit der Signatur der Methode weitgehend verschwinden.

Eine einfache Kopierschleife ist so einfach, dass offensichtliche Optimierungen darauf angewendet werden können. Zum Beispiel das Abrollen der Schleife. Was genau passiert, hängt wiederum von der Implementierung ab.

Tom Hawtin - Tackline
quelle
2
Dies ist eine sehr anständige Antwort :), insb. das Erwähnen der Intrinsics. Ohne sie könnte eine einfache Iteration schneller sein, da sie normalerweise sowieso von der JIT
abgewickelt wird
4

In meinen eigenen Tests ist System.arraycopy () zum Kopieren von Arrays mit mehreren Dimensionen 10 bis 20 Mal schneller als das Verschachteln von Schleifen:

float[][] foo = mLoadMillionsOfPoints(); // result is a float[1200000][9]
float[][] fooCpy = new float[foo.length][foo[0].length];
long lTime = System.currentTimeMillis();
System.arraycopy(foo, 0, fooCpy, 0, foo.length);
System.out.println("native duration: " + (System.currentTimeMillis() - lTime) + " ms");
lTime = System.currentTimeMillis();

for (int i = 0; i < foo.length; i++)
{
    for (int j = 0; j < foo[0].length; j++)
    {
        fooCpy[i][j] = foo[i][j];
    }
}
System.out.println("System.arraycopy() duration: " + (System.currentTimeMillis() - lTime) + " ms");
for (int i = 0; i < foo.length; i++)
{
    for (int j = 0; j < foo[0].length; j++)
    {
        if (fooCpy[i][j] != foo[i][j])
        {
            System.err.println("ERROR at " + i + ", " + j);
        }
    }
}

Dies druckt:

System.arraycopy() duration: 1 ms
loop duration: 16 ms
Jumar
quelle
8
Auch wenn diese Frage alt ist, nur zur Veranschaulichung: Dies ist KEIN fairer Benchmark (geschweige denn die Frage, ob ein solcher Benchmark überhaupt Sinn macht). System.arraycopyführt eine flache Kopie durch (nur die Verweise auf die inneren float[]s werden kopiert), während Ihre verschachtelten forSchleifen eine tiefe Kopie ( floatvon float) ausführen . Eine Änderung an fooCpy[i][j]wird in der fooVerwendung berücksichtigt System.arraycopy, verwendet jedoch nicht die verschachtelten forSchleifen.
Misberner
3

Es gibt einige Gründe:

  1. Es ist unwahrscheinlich, dass die JIT so effizienten Low-Level-Code generiert wie ein manuell geschriebener C-Code. Die Verwendung von Low Level C kann viele Optimierungen ermöglichen, die für einen generischen JIT-Compiler nahezu unmöglich sind.

    Unter diesem Link finden Sie einige Tricks und Geschwindigkeitsvergleiche von handgeschriebenen C-Implementierungen (memcpy, aber das Prinzip ist dasselbe): Aktivieren Sie dieses Kontrollkästchen Optimieren von Memcpy verbessert die Geschwindigkeit

  2. Die C-Version ist weitgehend unabhängig von Typ und Größe der Array-Mitglieder. In Java ist dies nicht möglich, da es keine Möglichkeit gibt, den Array-Inhalt als unformatierten Speicherblock (z. B. Zeiger) abzurufen.

Hrvoje Prgeša
quelle
1
Java-Code kann optimiert werden. Tatsächlich wird Maschinencode generiert, der effizienter ist als der von C.
Tom Hawtin - Tackline
Ich bin damit einverstanden, dass JITed-Code manchmal besser lokal optimiert wird, da er weiß, auf welchem ​​Prozessor er ausgeführt wird. Da es jedoch "just in time" ist, kann es niemals alle nicht lokalen Optimierungen verwenden, deren Ausführung länger dauert. Außerdem kann es niemals mit dem handgefertigten C-Code übereinstimmen (der auch den Prozessor berücksichtigen und die JIT-Vorteile teilweise zunichte machen könnte, entweder durch Kompilieren für einen bestimmten Prozessor oder durch eine Art Laufzeitprüfung).
Hrvoje Prgeša
1
Ich denke, dass das Sun JIT-Compilerteam viele dieser Punkte bestreiten würde. Ich glaube zum Beispiel, dass HotSpot eine globale Optimierung durchführt, um unnötiges Versenden von Methoden zu vermeiden, und es gibt keinen Grund, warum eine JIT keinen prozessorspezifischen Code generieren kann. Dann gibt es den Punkt, an dem ein JIT-Compiler eine Verzweigungsoptimierung basierend auf dem Ausführungsverhalten des aktuellen Anwendungslaufs durchführen kann.
Stephen C
@Stephen C - Hervorragender Punkt zu den Verzweigungsoptimierungen, obwohl Sie auch statische Leistungsprofile mit C / C ++ - Compilern durchführen können, um den ähnlichen Effekt zu erzielen. Ich denke auch, dass der Hotspot zwei Betriebsmodi hat - Desktop-Anwendungen verwenden nicht alle verfügbaren Optimierungen, um eine angemessene Startzeit zu erreichen, während die Server-Anwendungen aggressiver optimiert werden. Alles in allem erhalten Sie einige Vorteile, aber Sie verlieren auch einige.
Hrvoje Prgeša
1
System.arrayCopy wird nicht mit C implementiert, was diese Antwort irgendwie ungültig macht
Nitsan Wakart