35 votes

C #: Cette classe de benchmarking est-elle précise?

J'ai créé une classe simple pour comparer certaines de mes méthodes. Mais est-ce exact? Je suis un peu novice en matière d'analyse comparative, de chronométrage, etc., alors je pensais pouvoir demander votre avis ici. De plus, si c'est bon, quelqu'un d'autre pourrait peut-être l'utiliser aussi :)

 public static class Benchmark
{
    public static IEnumerable<long> This(Action subject)
    {
        var watch = new Stopwatch();
        while (true)
        {
            watch.Reset();
            watch.Start();
            subject();
            watch.Stop();
            yield return watch.ElapsedTicks;
        }
    }
}
 

Vous pouvez l'utiliser comme ceci:

 var avg = Benchmark.This(() => SomeMethod()).Take(500).Average();
 

Tous les commentaires? Cela semble-t-il assez stable et précis ou ai-je oublié quelque chose?

21voto

Henk Holterman Points 153608

Il est à peu près aussi précis que vous pouvez obtenir pour un simple indice de référence. Mais il ya certains facteurs qui ne sont pas sous votre contrôle:

  • la charge sur le système à partir d'autres processus
  • état de la pile avant/pendant l'indice de référence

Vous pourriez faire quelque chose à propos de ce dernier point, un indice de référence est l'une des rares situations où l'appelant GC.Collect peut être défendu. Et que l'on pourrait appeler subject une fois à l'avance afin d'éliminer toute JIT questions. Mais cela nécessite des appels d' subject à être indépendant.

public static IEnumerable<TimeSpan> This(Action subject)
{
    subject();     // warm up
    GC.Collect();  // compact Heap
    GC.WaitForPendingFinalizers(); // and wait for the finalizer queue to empty

    var watch = new Stopwatch();
    while (true)
    {
        watch.Reset();
        watch.Start();
        subject();
        watch.Stop();
        yield return watch.Elapsed;  // TimeSpan
    }
}

Pour le bonus, votre classe doit vérifier l' System.Diagnostics.Stopwatch.IsHighResolution champ. Si elle est désactivée, vous n'avez qu'une très grossier (20 ms) de la résolution.

Mais sur un PC classique, avec de nombreux services en cours d'exécution en arrière-plan, il n'est jamais va être très précis.

10voto

Eric Lippert Points 300275

Quelques problèmes ici.

Tout d'abord, rappelez-vous que la première fois que vous exécutez le code, la fermeture transitive de ses appels de méthode sera jitted. Cela signifie que la première exécution est susceptible d'avoir un coût plus élevé que tous les exécuter. Selon que vous êtes benchmarking "à froid" minutage ou "à chaud" des timings, cela pourrait faire une différence. J'ai vu des méthodes où le coût de la jitting la méthode a été plus élevé que tous les autres de l'appel à elle ensemble!

Deuxièmement, rappelez-vous que le garbage collector s'exécute sur un autre thread. Si vous faites des ordures dans un seul terme, le coût de nettoyage de la poubelle peut-être pas rendu compte jusqu'à ce que suebsequent s'exécute. Vous êtes donc à défaut de compte pour le coût total d'un terme, par foisting il sur, plus tard, s'exécute.

Deux de ceux-ci sont révélateurs de la faiblesse de benchmarking: analyse comparative est, par nature, irréaliste, et, par conséquent, de peu de valeur. Dans le monde réel, le code, le GC va être en cours d'exécution, la gigue va être en cours d'exécution, et ainsi de suite. C'est souvent le cas que comparé la performance n'est rien du tout comme dans le monde réel les performances, car l'indice de référence ne tient pas compte de la variabilité du monde réel les coûts inhérents à un système plus large. Plutôt que de l'analyser perf caractéristiques dans l'isolement, je préfère regarder les perf caractéristiques de scénarios réalistes réellement supportés par de vrais clients.

7voto

MusiGenesis Points 49273

Vous devriez certainement de retour ElapsedMilliseconds au lieu de ElapsedTicks. La valeur retournée par ElapsedTicks dépend du Chronomètre fréquence, qui peut être différent sur différents systèmes. Il ne correspondra pas nécessairement à la propriété Ticks d'un Temps ou un objet DateTime.

Voir http://msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch.elapsedticks.aspx.

Si vous ne voulez que la résolution supplémentaire de Tiques, vous devez retourner watch.Elapsed.Ticks (c'est à dire d'Horodatage.Les tiques) au lieu de watch.ElapsedTicks (ce pourrait être l'un des plus subtiles potentiel d' erreurs .Net). À partir de MSDN:

Chronomètre tiques sont différents de DateTime.Les tiques. À chaque itération dans le DateTime.Les tiques valeur représente un 100 nanosecondes intervalle. Chaque tick le ElapsedTicks valeur représente le intervalle de temps égal à 1 seconde divisé par la Fréquence.

Autre que cela, je suppose que ton code est très bien, bien que je pense que vous auriez y compris certains de la méthode-l'appel de la surcharge dans vos mesures, qui pourrait être important si les méthodes elles-mêmes de prendre très peu de temps à s'exécuter. Aussi, vous allez probablement vouloir exclure le premier appel à la méthode de votre moyenne calculée, mais je ne suis pas sûr comment vous pouvez le faire dans votre classe.

Un dernier point, qui n'est probablement pas pertinente pour la plupart des utilisations de cette classe: Chronomètre fonctionne un peu rapide par rapport à l'heure du système. Sur mon ordinateur, il est environ 5 secondes (c'est secondes, pas millisecondes) avant au bout de 24 heures, et sur d'autres machines de cette dérive peut être encore plus grande. Donc c'est un peu trompeur de dire que c'est très précis, quand il fait juste très granulaire. Pour le calendrier de courte durée de méthodes, de toute évidence, cela ne serait pas un problème important.

Et encore un dernier point, qui certainement est pertinente: j'ai souvent constaté lors de l'analyse comparative que je vais obtenir un tas de temps de course qui sont tous regroupés dans une fourchette étroite de valeurs (par exemple, 80, 80, 79, 82, etc.), mais de temps en temps quelque chose d'autre se passe dans Windows (comme l'ouverture d'un autre programme ou mon anti-virus coups de pied ou quelque chose) et je vais obtenir une valeur sauvagement hors de contrôle avec les autres (par exemple, 80, 80, 79, 271, 80 etc.). Je pense que une solution simple à ce aberrantes problème est l'utilisation de la médiane de vos mesures, au lieu de la moyenne. Je ne sais pas si Linq prend en charge automatiquement ou pas.

2voto

ZachS Points 782

Comme je ne suis pas un programmeur C#, je ne peux pas le dire avec beaucoup de précision si cette classe est une implémentation appropriée pour compter combien de temps une fonction d'exécution. Cependant, il y a des choses à garder à l'esprit pour la répétabilité et la précision.

Je ne suis pas en place sur les différents tenants et les aboutissants de la .NET Framework, mais en fonction de comment il compile en code natif, il pourrait être possible que toute compilation aurait une incidence sur les résultats d'un benchmark. Aussi, si oui ou non une fonction est dans le cache peut faire une différence, aussi. Vous devez donc faire une boucle sur votre fonction afin de s'assurer qu'il n'est pas frappé de la compilation, et que tout est chargé et prêt. Une fois cela fait, vous pourriez être en mesure de commencer.

D'autres vont probablement avoir une meilleure information et des connaissances .NET que je fais.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X