C # – internes de la boucle for

une question simple et rapide de ma part au sujet des boucles d’attente.

Situation J’écris actuellement du code très performant alors que je me demandais soudainement comment se comportait la boucle for. Je sais que je suis tombé par hasard sur cela auparavant, mais je ne peux pas pour la vie retrouver cette information: /

Néanmoins, ma principale préoccupation concernait le limiteur. Disons que nous avons:

for(int i = 0; i < something.awesome; i++) { // Do cool stuff } 

Question Quelque chose.awesome est stocké en tant que variable interne ou la boucle récupère-t-elle constamment quelque chose.impressionnant pour effectuer la vérification logique? La raison pour laquelle je pose cette question est bien sûr parce que je dois parcourir beaucoup de choses indexées et que je ne veux vraiment pas du temps système supplémentaire d’appel de fonction pour chaque passe.

Cependant, si quelque chose.impressionnant n’est appelé qu’une fois, je retourne sous mon joyeux rocher! 🙂

Vous pouvez utiliser un exemple de programme simple pour vérifier le comportement:

 using System; class Program { static int GetUpperBound() { Console.WriteLine("GetUpperBound called."); return 5; } static void Main(ssortingng[] args) { for (int i = 0; i < GetUpperBound(); i++) { Console.WriteLine("Loop iteration {0}.", i); } } } 

La sortie est la suivante:

 GetUpperBound called. Loop iteration 0. GetUpperBound called. Loop iteration 1. GetUpperBound called. Loop iteration 2. GetUpperBound called. Loop iteration 3. GetUpperBound called. Loop iteration 4. GetUpperBound called. 

Les détails de ce comportement sont décrits dans la Spécification du langage C # 4.0, section 8.3.3 (vous trouverez la spécification à l'intérieur de C: \ Programmes \ Microsoft Visual Studio 10.0 \ VC # \ Specifications \ 1033 ):

Une instruction for est exécutée comme suit:

  • Si un for-initializer est présent, les expressions d'initialisation ou d'instructions de variable sont exécutées dans l'ordre dans lequel elles ont été écrites. Cette étape n'est effectuée qu'une fois.

  • Si une condition for est présente, elle est évaluée.

  • Si la condition for n'est pas présente ou si l'évaluation renvoie true, le contrôle est transféré à l'instruction incorporée. Quand et si le contrôle atteint le point final de l'instruction incorporée (éventuellement à partir de l'exécution d'une instruction continue), les expressions du for-iterator, le cas échéant, sont évaluées dans l'ordre, puis une autre itération est effectuée, en commençant par l'évaluation de la pour condition dans l'étape ci-dessus.

  • Si la condition for est présente et que l'évaluation aboutit à false, le contrôle est transféré au sharepoint terminaison de l'instruction for.

Si quelque chose.awesome est un champ, il est susceptible d’être accessible à chaque fois autour de la boucle, car quelque chose dans le corps de la boucle peut le mettre à jour. Si le corps de la boucle est assez simple et n’appelle aucune méthode (à l’exception des méthodes insérées par le compilateur), le compilateur peut alors prouver qu’il est prudent de mettre la valeur de quelquechose.awesome dans un registre. Les rédacteurs de compilateurs avaient l’habitude de trop se permettre de le faire.

Cependant, de nos jours, il faut beaucoup de temps pour accéder à une valeur de la mémoire principale, mais une fois que la valeur a été lue pour la première fois, elle est dirigée par le processeur. La lecture de la valeur pour la deuxième fois à partir du cache de la CPU est beaucoup plus rapide que de la lire dans un registre, puis de la lire dans la mémoire principale. Il n’est pas rare qu’un cache de processeur soit des centaines de fois plus rapide que la mémoire principale.

Désormais, si quelque chose.awesome est une propriété , il s’agit en fait d’un appel de méthode. Le compilateur appellera la méthode à chaque fois autour de la boucle. Cependant, si la propriété / méthode ne contient que quelques lignes de code, le compilateur peut le prendre en ligne. Inlineing se produit lorsque le compilateur insère directement une copie du code de la méthode au lieu d’appeler la méthode. Ainsi, une propriété qui renvoie simplement la valeur d’un champ se comportera de la même manière que l’exemple de champ ci-dessus.

Evan lorsque la propriété n’est pas en ligne, elle sera dans le cache de la CPU après avoir été appelée pour la première fois. Donc, c’est très complexe ou bien la boucle se répète souvent, c’est beaucoup plus long d’appeler pour la première fois, peut-être plus d’un facteur 10.

Auparavant , c’était facile parce que tous les access à la mémoire et toutes les actions du processeur prenaient à peu près le même temps. De nos jours, le cache du processeur peut facilement modifier le minutage de certains access en mémoire et appels de méthodes par un facteur supérieur à 100 . Les profileurs ont tendance à supposer que tous les access à la mémoire prennent le même temps! Donc, si vous profilez, on vous demandera d’apporter des modifications qui n’auront aucun effet dans le monde réel.

Changer le code pour:

 int limit = something.awesome; for(int i = 0; i < limit; i++) { // Do cool stuff } 

Dans certains cas, cela va l'étaler, mais le rend aussi plus complexe. toutefois

 int limit = myArray.length; for(int i = 0; i < limit; i++) { myArray[i[ = xyn; } 

est plus lent alors

 for(int i = 0; i < myArray.length; i++) { myArray[i[ = xyn; } 

as .net vérifie la limite des tableaux à chaque access et dispose d’une logique pour supprimer la vérification lorsque la boucle est assez simple.

Il est donc préférable de garder le code simple et clair jusqu'à ce que vous puissiez prouver qu'il y a un problème. Vous gagnez beaucoup plus en dépensant votre temps à améliorer la conception générale d'un système. C'est facile à faire si le code que vous utilisez est simple.

Il a évalué à chaque fois. Essayez ceci dans une application console simple:

 public class MyClass { public int Value { get { Console.WriteLine("Value called"); return 3; } } } 

Utilisé de cette façon:

 MyClass myClass = new MyClass(); for (int i = 0; i < myClass.Value; i++) { } 

Il en résultera trois lignes imprimées à l'écran.

Mettre à jour

Donc, pour éviter cela, vous pourriez ceci:

 int awesome = something.awesome; for(int i = 0; i < awesome; i++) { // Do cool stuff } 

something.awesome chose.impressionnant sera réévalué chaque fois que vous parcourez la boucle.

Il serait préférable de faire ceci:

 int limit = something.awesome; for(int i = 0; i < limit; i++) { // Do cool stuff } 

Chaque fois que le compilateur récupérera la valeur de quelque chose.impressionnant et l’évaluera

La condition est évaluée à chaque fois, y compris la récupération de la valeur de something.awesome . Si vous voulez éviter cela, définissez une variable temporaire sur something.awesome chose.awesome et comparez-la à la variable temporaire.

Ce serait stocker une variable et la supprimer après utilisation.

using (int limit = quelque chose.impressionnant)
{
pour (int i = 0; i {
//Code.
}
}

De cette façon, il ne vérifiera pas à chaque fois.