Récemment, j'ai remarqué que déclarer un tableau contenant 64 éléments est beaucoup plus rapide (> 1000 fois) que de déclarer le même type de tableau avec 65 éléments.
Voici le code que j'ai utilisé pour tester ceci:
public class Tests{
public static void main(String args[]){
double start = System.nanoTime();
int job = 100000000;//100 million
for(int i = 0; i < job; i++){
double[] test = new double[64];
}
double end = System.nanoTime();
System.out.println("Total runtime = " + (end-start)/1000000 + " ms");
}
}
Cela dure environ 6 ms, si je remplace new double[64]
par new double[65]
cela, cela prend environ 7 secondes. Ce problème devient exponentiellement plus grave si le travail est réparti sur de plus en plus de threads, d'où mon problème.
Ce problème se produit également avec différents types de tableaux tels que int[65]
ou String[65]
. Ce problème ne se produit pas avec des chaînes volumineuses:, String test = "many characters";
mais commence à se produire lorsque cela est modifié enString test = i + "";
Je me demandais pourquoi c'est le cas et s'il est possible de contourner ce problème.
System.nanoTime()
devrait être préféréSystem.currentTimeMillis()
à l'analyse comparative.byte
au lieu dedouble
.Réponses:
Vous observez un comportement causé par les optimisations effectuées par le compilateur JIT de votre machine virtuelle Java. Ce comportement est reproductible déclenché avec des tableaux scalaires jusqu'à 64 éléments et n'est pas déclenché avec des tableaux supérieurs à 64.
Avant d'entrer dans les détails, examinons de plus près le corps de la boucle:
Le corps n'a aucun effet (comportement observable) . Cela signifie que cela ne fait aucune différence en dehors de l'exécution du programme, que cette instruction soit exécutée ou non. La même chose est vraie pour toute la boucle. Il peut donc arriver que l'optimiseur de code traduise la boucle en quelque chose (ou rien) avec le même comportement de synchronisation fonctionnel et différent.
Pour les benchmarks, vous devez au moins respecter les deux directives suivantes. Si vous l'aviez fait, la différence aurait été beaucoup plus petite.
Passons maintenant aux détails. Il n'est pas surprenant qu'une optimisation soit déclenchée pour les tableaux scalaires ne dépassant pas 64 éléments. L'optimisation fait partie de l' analyse Escape . Il place de petits objets et de petits tableaux sur la pile au lieu de les allouer sur le tas - ou mieux encore les optimise entièrement. Vous pouvez trouver quelques informations à ce sujet dans l'article suivant de Brian Goetz écrit en 2005:
L'optimisation peut être désactivée avec l'option de ligne de commande
-XX:-DoEscapeAnalysis
. La valeur magique 64 pour les tableaux scalaires peut également être modifiée sur la ligne de commande. Si vous exécutez votre programme comme suit, il n'y aura aucune différence entre les tableaux de 64 et 65 éléments:Cela dit, je déconseille fortement d'utiliser de telles options de ligne de commande. Je doute que cela fasse une énorme différence dans une application réaliste. Je ne l'utiliserais que si j'étais absolument convaincu de la nécessité - et non sur la base des résultats de certains pseudo benchmarks.
la source
Il existe plusieurs façons de faire une différence en fonction de la taille d'un objet.
Comme nosid l'a indiqué, le JITC peut (très probablement) allouer de petits objets "locaux" sur la pile, et la taille limite pour les "petits" tableaux peut être de 64 éléments.
L'allocation sur la pile est beaucoup plus rapide que l'allocation en tas et, plus précisément, la pile n'a pas besoin d'être récupérée, donc la surcharge du GC est considérablement réduite. (Et pour ce cas de test, la surcharge du GC est probablement de 80 à 90% du temps total d'exécution.)
En outre, une fois que la valeur est allouée à la pile, le JITC peut effectuer une "élimination du code mort", déterminer que le résultat de l '
new
n'est jamais utilisé nulle part, et, après s'être assuré qu'il n'y a aucun effet secondaire qui serait perdu, éliminer toute l'new
opération, puis la boucle (maintenant vide) elle-même.Même si le JITC ne fait pas d'allocation de pile, il est tout à fait possible que des objets plus petits qu'une certaine taille soient alloués dans un tas différemment (par exemple, à partir d'un «espace» différent) que des objets plus grands. (Cependant, cela ne produirait normalement pas de différences de synchronisation aussi dramatiques.)
la source