java - remplir - Vaut-il mieux utiliser System.arraycopy(...) qu'une boucle for pour copier des tableaux?




tableau d'objet java (5)

Je veux créer un nouveau tableau d'objets en assemblant deux tableaux plus petits.

Ils ne peuvent pas être NULL, mais la taille peut être 0.

Je ne peux pas choisir entre ces deux façons: sont-ils équivalents ou sont-ils plus efficaces (par exemple, system.arraycopy () copie des morceaux entiers)?

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

ou

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

La seule différence est-elle l'apparence du code?

EDIT: merci pour la question liée, mais ils semblent avoir une discussion non résolue:

Est-ce vraiment plus rapide si it is not for native types : byte [], Object [], char []? dans tous les autres cas, une vérification de type est exécutée, ce qui serait mon cas et serait donc équivalent ... non?

Sur une autre question liée, ils disent que the size matters a lot , pour une taille> 24 system.arraycopy () gagne, pour moins de 10, manuel pour la boucle est mieux ...

Maintenant, je suis vraiment confus.

https://code.i-harness.com


Au lieu de me fier à des spéculations et à des informations peut-être obsolètes, j'ai utilisé des étriers pour caliper certains points de référence. En fait, Caliper est livré avec quelques exemples, y compris un CopyArrayBenchmark qui mesure exactement cette question! Tout ce que vous avez à faire est de courir

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

Mes résultats sont basés sur la VM du serveur 64 bits Oracle Java HotSpot (TM), 1.8.0_31-b13, fonctionnant sur un MacBook Pro mi-2010 (macOS 10.11.6 avec un processeur Intel Arrandale i7, 8 GiB RAM). Je ne crois pas qu'il soit utile d'afficher les données de timing brutes. Je vais plutôt résumer les conclusions avec les visualisations de support.

En résumé:

  • Ecrire un manuel for boucle pour copier chaque élément dans un tableau nouvellement instancié n'est jamais avantageux, que ce soit pour des tableaux courts ou longs.
  • Arrays.copyOf( array , array .length) et array.clone() sont tous les deux rapidement rapides. Ces deux techniques sont presque identiques dans la performance; celui que vous choisissez est une question de goût.
  • System.arraycopy( src , 0, dest , 0, src .length) est presque aussi rapide que Arrays.copyOf( array , array .length) et array .clone() , mais pas tout à fait ainsi. (Voir le cas pour 50000 int s.) Pour cette raison, et la verbosité de l'appel, je recommanderais System.arraycopy() si vous avez besoin d'un contrôle précis sur les éléments à copier où.

Voici les graphiques de synchronisation:


Cela dépend de la machine virtuelle, mais System.arraycopy devrait vous donner le plus proche des performances natives.

J'ai travaillé pendant 2 ans en tant que développeur Java pour les systèmes embarqués (où la performance est une priorité énorme) et partout où System.arraycopy pourrait être utilisé, je l'ai surtout utilisé / vu utilisé dans le code existant. Il est toujours préféré sur les boucles lorsque la performance est un problème. Si la performance n'est pas un gros problème, j'irais avec la boucle, cependant. Beaucoup plus facile à lire.


L'exécution de méthodes natives telles que Arrays.copyOf(T[], int) a un certain coût mais cela ne signifie pas que ce n'est pas rapide car vous l'exécutez en utilisant JNI.

Le plus simple est d'écrire un benchmark et un test.

Vous pouvez vérifier que Arrays.copyOf(T[], int) est plus rapide que votre boucle normale.

Le code de référence d' here : -

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() utilise JNI (Java Native Interface) pour copier un tableau (ou des parties de celui-ci), il est donc incroyablement rapide, comme vous pouvez le confirmer here


Arrays.copyOf(T[], int) est plus facile à lire. Interne utilise System.arraycopy() qui est un appel natif.

Vous ne pouvez pas l'obtenir plus vite!


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

Je sais que les tests JUnit ne sont pas vraiment les meilleurs pour l'analyse comparative, mais
testHardCopyBytes a pris 0.157s pour terminer
et
testArrayCopyBytes a pris 0.086s pour terminer.

Je pense que cela dépend de la machine virtuelle, mais il semble que cela copie des blocs de mémoire au lieu de copier des éléments d'un seul tableau. Cela augmenterait absolument les performances.

MODIFIER:
Il semble que les performances de System.arraycopy soient partout. Lorsque les chaînes sont utilisées à la place des octets et que les tableaux sont petits (taille 10), j'obtiens les résultats suivants:

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

Voici à quoi cela ressemble quand les tableaux sont à la taille 0x1000000. Il semble que System.arraycopy gagne définitivement avec des tableaux plus grands.

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

Comment particulier!

Merci, Daren, de souligner que les références copient différemment. Cela en a fait un problème beaucoup plus intéressant!





java