2009-12-13 4 views
2

J'ai le code:C# * Problème étrange * avec StopWatch et une boucle foreach

var options = GetOptions(From, Value, SelectedValue);  
var stopWatch = System.Diagnostics.Stopwatch.StartNew(); 

foreach (Option option in options) 
{ 
    stringBuilder.Append("<option"); 

    stringBuilder.Append(" value=\""); 
    stringBuilder.Append(option.Value); 
    stringBuilder.Append("\""); 

    if (option.Selected) 
     stringBuilder.Append(" selected=\"selected\""); 

    stringBuilder.Append('>'); 

    stringBuilder.Append(option.Text); 
    stringBuilder.Append("</option>"); 
} 

HttpContext.Current.Response.Write("<b>" + stopWatch.Elapsed.ToString() + "</b><br>"); 

Il écrit:
00: 00: 00,0004255 dans le premier essai (pas debug)
00 : 00: 00.0004260 dans le deuxième essai et
00: 00: 00.0004281 dans le troisième essai.

Maintenant, si je change le code de sorte que la mesure sera l'intérieur la boucle foreach:

var options = GetOptions(From, Value, SelectedValue);  

foreach (Option option in options) 
{ 
    var stopWatch = System.Diagnostics.Stopwatch.StartNew(); 

    stringBuilder.Append("<option"); 

    stringBuilder.Append(" value=\""); 
    stringBuilder.Append(option.Value); 
    stringBuilder.Append("\""); 

    if (option.Selected) 
     stringBuilder.Append(" selected=\"selected\""); 

    stringBuilder.Append('>'); 

    stringBuilder.Append(option.Text); 
    stringBuilder.Append("</option>"); 

    HttpContext.Current.Response.Write("<b>" + stopWatch.Elapsed.ToString() + "</b><br>"); 
} 

... Je reçois
[00: 00: 00,0000014, 00: 00: 00,0000011] = 00: 00: 00.0000025 dans le premier essai (pas dans le débogage),
[00: 00: 00.0000016, 00: 00: 00.0000011] = 00: 00: 00.0000027 dans le deuxième essai et
[00: 00: 00.0000013 , 00: 00: 00.0000011] = 00: 00: 00.0000024 dans le troisième essai.

?!
Il est complètement insensé selon les premiers résultats ... J'ai entendu dire que la boucle foreach est lente, mais n'a jamais imaginé que c'est donc lente ... Est-ce que c'est?

options a 2 options. est ici la classe option, si elle est nécessaire:

public class Option 
{ 
    public Option(string text, string value, bool selected) 
    { 
     Text = text; 
     Value = value; 
     Selected = selected; 
    } 

    public string Text 
    { 
     get; 
     set; 
    } 

    public string Value 
    { 
     get; 
     set; 
    } 

    public bool Selected 
    { 
     get; 
     set; 
    } 
} 

Merci.

+0

Eh bien, quelle est la question? Est-ce "pourquoi cas 2 horloges plus rapide que le cas 1?" Ou est-ce "pourquoi" chaque boucle est si lente? " –

+0

pourquoi cas 2 horloges plus rapides que le cas 1 –

+0

@Alon: Je vois, donc vous demandez pourquoi l'itération à travers une boucle foreach (à l'exclusion des opérations dans la boucle) est si lente? Oui? –

Répondre

7

La boucle foreach elle-même n'a rien à voir avec la différence de temps.

Que retourne la méthode GetOptions? Je suppose qu'il ne retourne pas une collection d'options, mais plutôt un énumérateur capable d'obtenir les options. Cela signifie que la récupération des options n'est pas effectuée avant que vous ne commenciez à les itérer.

Dans le premier cas, vous démarrez l'horloge avant de commencer à répéter les options, ce qui signifie que le temps d'extraction des options est inclus dans le temps.

Dans le second cas, vous commencez l'horloge après à partir itérer les options, ce qui signifie que le temps pour aller chercher les options est pas inclus dans le temps. Donc, la différence de temps que vous voyez n'est pas due à la boucle foreach elle-même, c'est le temps qu'il faut pour aller chercher les options.

Vous pouvez vous assurer que les options sont immédiatement récupérés en les lisant dans une collection:

var options = GetOptions(From, Value, SelectedValue).ToList(); 

Maintenant mesurer la performance, et vous verrez très peu de différence.

0

Le premier exemple de code ne renvoie aucun résultat tant que toutes les options n'ont pas été itérées alors que la seconde ne sort qu'une fois que la première option a été traitée. S'il y a plusieurs options, vous vous attendez à voir une telle différence.

+0

Dans le deuxième exemple de code, j'ai écrit combien de temps chaque option prend dans chaque essai, et c'est loin des premiers résultats d'exemple de code. –

+0

De quel type sont les options? foreach créera un énumérateur qui devra être éliminé. L'élimination peut brûler certains cycles de cpu. – recursive

+0

options est un IEnumerable

1

Si vous mesurez 160 fois le temps nécessaire pour faire quelque chose, cela prendra en général 160 fois plus de temps que de mesurer le temps qu'il faut pour le faire une fois. Suggérez-vous que le contenu de la boucle ne soit exécuté qu'une seule fois, ou essayez-vous de comparer la craie et le fromage?

Dans le premier cas, essayez de changer la dernière ligne de votre code d'utiliser stopWatch.Elapsed.ToString() à stopWatch.Elapsed.ToString()/options.Count

Ce sera au moins moyenne vous comparez une itération avec une itération.

Cependant, vos résultats seront toujours inutiles. Le chronométrage d'une opération très courte donne une fois des résultats médiocres - vous devez répéter une telle chose des dizaines de milliers de fois pour obtenir un temps moyen statistiquement significatif. Sinon, l'imprécision de l'horloge système et les frais généraux impliqués dans le démarrage et l'arrêt de votre minuterie vont submerger vos résultats.

En outre, que fait le PC pendant tout ce temps? S'il y a d'autres processus chargeant le CPU, ils pourraient facilement interférer avec vos horaires. Si vous l'exécutez sur un serveur occupé, vous pouvez obtenir des résultats aléatoires compétitifs. Enfin, la façon dont vous excutez les tests peut modifier les choses. Si vous exécutez toujours le test 1 suivi du test 2, il est possible que l'exécution du premier test affecte les caches du processeur (par ex.des données dans la liste des options) etc. afin que le code suivant puisse s'exécuter plus rapidement. Si le garbage collection se produit au cours d'un de vos tests, il faussera les résultats.

Vous devez éliminer tous ces facteurs avant d'avoir des chiffres qui valent la comparaison. Alors seulement devriez-vous demander "pourquoi le test 1 fonctionne-t-il beaucoup plus lentement que le test 2"?

0

Juste pause it a few times in the IDE et vous verrez où le temps passe.

Il y a une tentation très naturelle et forte de penser que le temps que les choses prennent est proportionnel à la quantité de code qu'ils sont. Par exemple, à votre avis, lequel est le plus rapide?

for (MyClass x in y) 

for (MyClass theParticularInstanceOfClass in MyCollectionOfInstances) 

Il est naturel de penser que le premier est plus rapide, alors qu'en fait, la taille du code est hors de propos et pourrait se cacher une multitude d'opérations coûteuses.