Waarom is System.arraycopy native in Java?

Ik was verrast om in de Java-bron te zien dat System.arraycopy een native methode is.

Natuurlijk is de reden dat het sneller is. Maar welke native trucs kan de code gebruiken om hem sneller te maken?

Waarom loop je niet gewoon over de originele array en kopieer je elke pointer naar de nieuwe array – dit is toch niet zo langzaam en omslachtig?


Antwoord 1, autoriteit 100%

In native code kan dit worden gedaan met een enkele memcpy/ memmove, in tegenstelling tot nverschillende kopieerbewerkingen. Het prestatieverschil is aanzienlijk.


Antwoord 2, autoriteit 19%

Het kan niet in Java worden geschreven. Native code kan het verschil tussen arrays van Object en arrays van primitieven negeren of wegnemen. Java kan dat niet, althans niet efficiënt.

En het kan nietworden geschreven met een enkele memcpy(), vanwege de semantiek die vereist is voor overlappende arrays.


Antwoord 3, autoriteit 13%

Het is natuurlijk afhankelijk van de implementatie.

HotSpot behandelt het als een “intrinsiek” en voegt code in op de oproepsite. Dat is machinecode, geen langzame oude C-code. Dit betekent ook dat de problemen met de ondertekening van de methode grotendeels verdwijnen.

Een eenvoudige kopieerlus is zo eenvoudig dat er duidelijke optimalisaties op kunnen worden toegepast. Bijvoorbeeld lus uitrollen. Wat er precies gebeurt, is weer afhankelijk van de implementatie.


Antwoord 4, autoriteit 5%

In mijn eigen tests is System.arraycopy() voor het kopiëren van arrays met meerdere dimensies 10 tot 20 keer sneller dan interleaving for loops:

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

Hiermee wordt afgedrukt:

System.arraycopy() duration: 1 ms
loop duration: 16 ms

Antwoord 5, autoriteit 5%

Er zijn een paar redenen:

  1. Het is onwaarschijnlijk dat het JIT zo efficiënte low-level code genereert als een handmatig geschreven C-code. Het gebruik van laag niveau C kan veel optimalisaties mogelijk maken die bijna onmogelijk zijn voor een generieke JIT-compiler.

    Zie deze link voor enkele trucs en snelheidsvergelijkingen van handgeschreven C-implementaties (memcpy, maar het principe is hetzelfde): Check this Memcpy optimaliseren verbetert de snelheid

  2. De C-versie is vrijwel onafhankelijk van het type en de grootte van de arrayleden. Het is niet mogelijk om hetzelfde te doen in Java, omdat er geen manier is om de array-inhoud als een onbewerkt geheugenblok (bijv. pointer) te krijgen.

Other episodes