2010-05-05 8 views
74

J'ai été surpris de voir dans la source Java que System.arraycopy est une méthode native.Pourquoi System.arraycopy est-il natif en Java?

Bien sûr, la raison en est que c'est plus rapide. Mais quelles astuces natives est le code capable d'employer qui le rendent plus rapide? Pourquoi ne pas simplement boucler sur la matrice d'origine et copier chaque pointeur sur la nouvelle matrice - ce n'est pas si lent et encombrant?

Répondre

70

Dans le code natif, il peut être fait avec un seul memcpy/memmove, par opposition à n opérations de copie distinctes. La différence de performance est substantielle.

+0

@Peter, dans un délai de code natif, vous pouvez vous frayer avec le modèle de mémoire Java? (Je n'ai jamais eu aucune raison de faire un malarkey natif) –

+0

@James B, je ne suis pas un expert en la matière, mais en code natif, vous n'êtes sûrement pas contraint avec le modèle de mémoire Java ou quoi que ce soit - vous pouvez bricoler bits comme vous le souhaitez (à vos risques et périls, bien sûr). –

+7

En fait, seules certaines sous-classes de 'arraycopy' pourraient être implémentées en utilisant' memcpy'/'memmove'. D'autres nécessitent une vérification du type d'exécution pour chaque élément copié. –

14

Il ne peut pas être écrit en Java. Le code natif est capable d'ignorer ou d'élider la différence entre les tableaux d'objets et les tableaux de primitives. Java ne peut pas faire cela, du moins pas efficacement.

Et il ne peut pas être écrit avec un seul memcpy(), en raison de la sémantique requise par les tableaux se chevauchant.

+2

Bien, alors «memmove» alors. Bien que je ne pense pas que cela fasse une grande différence dans le contexte de cette question. –

+0

Pas memmove() non plus, voir les commentaires de @Stephen C sur une autre réponse. – EJP

+0

Déjà vu, puisque c'était ma propre réponse ;-) Mais merci quand même. –

3

Il y a quelques raisons:

  1. Le JIT est peu susceptible de générer le code de bas niveau efficace en tant que code C écrit manuellement. L'utilisation d'un niveau bas C peut permettre de nombreuses optimisations presque impossibles à faire pour un compilateur JIT générique.

    Voir ce lien pour quelques trucs et des comparaisons de vitesse de mise en oeuvre de C écrites à la main (memcpy, mais le principe est le même): Cochez cette case Optimizing Memcpy improves speed

  2. La version C est à peu près indépendante du type et la taille de les 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 de mémoire brut (par exemple, pointeur).

+1

Le code Java peut être optimisé. En fait, ce qui se passe réellement, c'est un code machine qui est plus efficace que le code C. –

+0

Je suis d'accord pour dire que parfois le code JIT sera mieux optimisé localement car il sait sur quel processeur il fonctionne. Cependant, puisqu'il est "juste à temps", il ne pourra jamais utiliser toutes ces optimisations non locales qui prennent plus de temps à s'exécuter. De plus, il ne sera jamais capable de faire correspondre le code C fait à la main (qui pourrait également prendre en compte le processeur et annuler partiellement les avantages du JIT, soit en compilant un processeur spécifique, soit en vérifiant le runtime). –

+1

Je pense que l'équipe du compilateur Sun JIT contesterait beaucoup de ces points. Par exemple, je crois que HotSpot fait une optimisation globale pour supprimer la distribution de méthodes inutiles, et il n'y a aucune raison pour qu'un JIT ne puisse pas générer de code spécifique au processeur. Il y a ensuite le point qu'un compilateur JIT peut effectuer une optimisation de branche en fonction du comportement d'exécution de l'exécution de l'application en cours. –

9

Il est, bien sûr, la mise en œuvre dépend.

HotSpot le traitera comme un code «intrinsèque» et insérera du code sur le site d'appel. C'est le code machine, pas le vieux code C lent. Cela signifie également que les problèmes liés à la signature de la méthode disparaissent en grande partie. Une simple boucle de copie est assez simple pour que des optimisations évidentes puissent lui être appliquées. Par exemple, dérouler la boucle. Ce qui se passe exactement dépend de l'implémentation.

+1

c'est une réponse très décente :), esp. la mention des intrinsèques. Les itérations simples peuvent être plus rapides car elles sont généralement déroulées de toute façon par le JIT. – bestsss

4

Dans mes propres tests System.arraycopy() pour copier des tableaux de dimensions multiples est 10 à 20 fois plus rapide que pour les boucles entrelacer:

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

Cette impression:

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

Même si cette question est ancienne, juste pour le compte rendu: Ce n'est PAS un juste repère (encore moins la question de savoir si un tel benchmark aurait du sens dans le première place). 'System.arraycopy' fait une copie superficielle (seules les * références * aux internes' float [] 'sont copiées), alors que les boucles' for' imbriquées effectuent une copie en profondeur ('float' par' float'). Une modification de 'fooCpy [i] [j]' sera reflétée dans 'foo' en utilisant' System.arraycopy', mais n'utilisera pas les boucles 'for' imbriquées. – misberner