C #, boucles For et test de vitesse & # 8230; Exactement la même boucle plus rapide la deuxième fois?

StackOverflow https://stackoverflow.com/questions/303343

Question

public Int64 ReturnDifferenceA()
{
  User[] arrayList;
  Int64 firstTicks;
  IList<User> userList;
  Int64 secondTicks;
  System.Diagnostics.Stopwatch watch;

  userList = Enumerable
              .Range(0, 1000)
              .Select(currentItem => new User()).ToList();

  arrayList = userList.ToArray();

  watch = new Stopwatch();
  watch.Start();

  for (Int32 loopCounter = 0; loopCounter < arrayList.Count(); loopCounter++)
  {
     DoThings(arrayList[loopCounter]);
  }

  watch.Stop();
  firstTicks = watch.ElapsedTicks;

  watch.Reset();
  watch.Start();
  for (Int32 loopCounter = 0; loopCounter < arrayList.Count(); loopCounter++)
  {
     DoThings(arrayList[loopCounter]);
  }
  watch.Stop();
  secondTicks = watch.ElapsedTicks;

  return firstTicks - secondTicks;
}

Comme vous pouvez le constater, c’est très simple. Créez une liste d'utilisateurs, forcez sur un tableau, démarrez une surveillance, passez en boucle la liste et appelez une méthode, arrêtez la surveillance. Répéter. Terminez en retournant la différence entre le premier et le second.

Maintenant, je vous appelle avec ces:

differenceList = Enumerable
                 .Range(0, 50)
                 .Select(currentItem => ReturnDifferenceA()).ToList();
average = differenceList.Average();

differenceListA = Enumerable
                  .Range(0, 50)
                  .Select(currentItem => ReturnDifferenceA()).ToList();
averageA = differenceListA.Average();

differenceListB = Enumerable
                  .Range(0, 50)
                  .Select(currentItem => ReturnDifferenceA()).ToList();
averageB = differenceListB.Average();

Ce qui est amusant, c’est que toutes les moyennes sont relativement positives, allant de 150 000 à 300 000 ticks.

Ce que je ne comprends pas, c'est que je parcours la même liste, de la même manière, avec la même méthode et pourtant, il y a une telle différence. Y a-t-il une sorte de cache en cours?

Une autre chose intéressante est que si je parcoure la liste AVANT le premier chronomètre, les moyennes sont d’environ 5k.

Était-ce utile?

La solution

Soit dit en passant, l’utilisation de IEnumerable.Count () sur un tableau est des centaines de fois plus lente que Array.Length ... Bien que cela ne réponde pas du tout à la question.

Autres conseils

Vous utilisez un langage de haut niveau avec un environnement d’exécution qui effectue beaucoup d’optimisations de la mise en cache et des performances. C’est courant. Parfois, cela s'appelle réchauffer la machine virtuelle ou réchauffer le serveur (lorsqu'il s'agit d'une application de production).

Si quelque chose doit être fait de manière répétée, vous remarquerez fréquemment que la première fois a une durée d'exécution plus longue et que le reste devrait se stabiliser à un niveau inférieur.

Je le fais dans le code MATLAB et constate que la première fois que je lance une boucle de référence, cela prend cinq secondes et les temps suivants, un cinquième de seconde. C'est une différence énorme, car il s'agit d'un langage interprété qui nécessitait une certaine forme de compilation, mais en réalité, cela n'affectera pas votre performance, car la grande majorité sera une deuxième fois dans toute application de production.

Il est tout à fait possible que DoThings () ne soit pas compilé par JIT en code natif avant son appel initial.

Parce que .NET, comme la plate-forme Java, est un environnement JIT. Tout le code .NET de haut niveau est compilé en bytecode de langue intermédiaire de Microsoft.

Pour exécuter votre programme, ce bytecode doit être compilé / traduit en code machine natif. Toutefois, les fichiers programmés .NET compilés ne sont pas stockés dans le code machine natif, mais dans le bytecode de la machine virtuelle intermédiaire.

La première exécution est JIT compilée, donc cela a pris du temps supplémentaire. Les exécutions suivantes ne doivent plus nécessairement être compilées JIT, mais le code natif est tiré du cache JIT, il devrait donc être plus rapide.

Avez-vous conservé votre application sans terminer les exécutions suivantes? Ensuite, la deuxième raison est également due à VM. (VM: 1 = machine virtuelle; VM: 2 = mémoire virtuelle). Tous les systèmes d'exploitation généralisés modernes exécutent leurs processus sur la mémoire virtuelle, qui est une carte de la mémoire réelle, afin de permettre au système d'exploitation de gérer et d'optimiser l'utilisation des ressources système. Les processus les moins utilisés sont fréquemment transférés dans la mémoire cache sur disque afin de permettre aux autres processus d’utiliser les ressources de manière optimale.

Votre processus n’était pas dans la mémoire virtuelle la première fois. Il doit donc supporter le surcoût d’être balayé en mémoire. Dans la mesure où, par la suite, votre processus figurait parmi la dernière liste la plus récemment utilisée (en bas de la liste la moins récemment utilisée), il n'a pas encore été balayé dans le cache du disque.

En outre, le système d’exploitation répartit les ressources en fonction des besoins. Ainsi, pour le premier tour, votre processus a dû surmonter la peine de pousser le conflit d’enveloppes avec le système d’exploitation pour élargir les limites de ses ressources.

Une machine virtuelle permet à .NET et à Java d’abréger la plupart des fonctionnalités de programmation dans une couche indépendante de la machine, en les séparant et en laissant ainsi un désordre moins important aux ingénieurs logiciels dépendant de la machine. Même si Microsoft Windows fonctionne sur du matériel descendant x86 plutôt unifié, il existe suffisamment de différences entre les versions de système d'exploitation et les modèles de CPU pour justifier l'utilisation d'une machine virtuelle abstraite afin de donner aux programmeurs .NET et aux utilisateurs une vue cohérente.

Vous dites que vous ne le comprenez pas trois fois, les deuxième et troisième fois sont relativement proches. Il me semble que ce n’est que la première fois dans la boucle que les choses vont lentement.

Je suppose que la fonction que vous appelez n’est pas Just-In-Timed jusqu’à la première exécution. Ce que vous pouvez essayer, c'est de l'exécuter une fois, puis de l'arrêter et de l'exécuter à nouveau. En l'absence de changement de code, la compilation Just-In-Time de l'exécution précédente devrait tout de même être correcte, et les optimisations restantes que vous voyez sont les effets de mise en cache réels au travail.

Laissons de côté la question de l’échauffement de la machine virtuelle ou de la machine, de la mise en cache, des optimisations JIT, pour un moment: que fait votre ordinateur en plus? Est-ce que des services système et des tâches de la barre des tâches 3e42 récupèrent de l’UC? Peut-être votre client Steam a-t-il décidé de rechercher des mises à jour, ou IE devait-il faire quelque chose d'extrêmement important, ou votre programme antivirus vous gênait?

Votre test est aussi utile que la mesure dans laquelle vous pouvez l’isoler de tous les autres logiciels exécutés sur votre ordinateur. Désactivez tous les logiciels possibles avant de tenter de mesurer une course.

Mais alors que sais-je? - peut-être que votre méthode de mesure est gérée par le runtime .net (ou autre) aussi et par des comptes pour seulement les 'cycles virtuels' du runtime.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top