J'ai été surpris de voir dans la source Java que System.arraycopy est une méthode native.
Bien sûr, la raison en est que c'est plus rapide. Mais quelles astuces natives le code peut-il utiliser pour le rendre plus rapide?
Pourquoi ne pas simplement parcourir le tableau d'origine et copier chaque pointeur dans le nouveau tableau - ce n'est sûrement pas si lent et encombrant?
En code natif, cela peut être fait avec un seul memcpy
/ memmove
, par opposition à n opérations de copie distinctes. La différence de performances est substantielle.
Il ne peut pas être écrit en Java. Le code natif est capable d'ignorer ou d'éliminer la différence entre les tableaux d'objets et les tableaux de primitives. Java ne peut pas faire ça, du moins pas efficacement.
Et il ne peut pas être écrit avec une seule memcpy()
, à cause de la sémantique requise par les tableaux qui se chevauchent.
Cela dépend, bien sûr, de la mise en œuvre.
HotSpot le traitera comme "intrinsèque" et insérera du code sur le site de l'appel. C'est le code machine, pas le vieux code C lent. Cela signifie également que les problèmes de signature de la méthode disparaissent largement.
Une boucle de copie simple est suffisamment simple pour que des optimisations évidentes puissent lui être appliquées. Par exemple, le déroulement d'une boucle. Ce qui se passe exactement dépend à nouveau de l'implémentation.
Dans mes propres tests, System.arraycopy () pour copier des tableaux à plusieurs dimensions est 10 à 20 fois plus rapide que l'entrelacement pour les boucles:
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);
}
}
}
Cela imprime:
System.arraycopy() duration: 1 ms
loop duration: 16 ms
Il y a quelques raisons:
Il est peu probable que le JIT génère un code de bas niveau aussi efficace qu'un code C écrit manuellement. L'utilisation de bas niveau C peut permettre de nombreuses optimisations qui sont presque impossibles à faire pour un compilateur JIT générique.
Voir ce lien pour quelques astuces et comparaisons de vitesse des implémentations C écrites à la main (memcpy, mais le principe est le même): Vérifiez ceci L'optimisation de Memcpy améliore la vitesse
La version C est à peu près indépendante du type et de la taille des membres du tableau. Il n'est pas possible de faire la même chose dans Java car il n'y a aucun moyen d'obtenir le contenu du tableau comme un bloc brut de mémoire (par exemple un pointeur).