Mesurer le temps d'exécution d'une fonction en C ++


138

Je veux savoir combien de temps une certaine fonction prend dans mon programme C ++ pour s'exécuter sous Linux . Ensuite, je veux faire une comparaison de vitesse. J'ai vu plusieurs fonctions de temps mais j'ai fini avec cela de boost. Chrono:

process_user_cpu_clock, captures user-CPU time spent by the current process

Maintenant, je ne suis pas clair si j'utilise la fonction ci-dessus, vais-je obtenir le seul temps que le processeur a passé sur cette fonction?

Deuxièmement, je n'ai trouvé aucun exemple d'utilisation de la fonction ci-dessus. Quelqu'un peut-il m'aider à utiliser la fonction ci-dessus?

PS: En ce moment, j'utilise std::chrono::system_clock::now()pour obtenir le temps en secondes mais cela me donne des résultats différents en raison de la charge CPU différente à chaque fois.


2
Pour une utilisation Linux: clock_gettime.. gcc définit les autres horloges comme suit: typedef system_clock steady_clock; typedef system_clock high_resolution_clock;sous Windows, utilisez QueryPerformanceCounter.
Brandon

Cette question n'est-elle pas une réplique de celle-ci ou les scénarios rendent-ils les solutions différentes?
nord du

J'ai deux implémentations d'une fonction et j'aimerais trouver celle qui fonctionne le mieux.
nord du

Très important: assurez-vous d'activer l'optimisation . Un Code optimisé a différents goulots d' étranglement que le code Optimized normal et ne pas vous dire quoi que ce soit significatif. Aide à l'optimisation de la boucle C pour l'affectation finale (avec l'optimisation du compilateur désactivée) . Et en général, le microbenchmarking comporte de nombreux pièges, en particulier l'échec de faire d'abord une boucle de préchauffage pour les défauts de fréquence du processeur et de page: Méthode idiomatique d'évaluation des performances? . Et cette réponse
Peter Cordes

Voir aussi Comment évalueriez-vous les performances d'une fonction? pour Google Benchmark qui évite de nombreux écueils liés au déploiement de votre propre microbenchmark. De plus, le test de performance de la boucle Simple for () prend le même temps avec n'importe quelle boucle liée pour en savoir plus sur la façon dont l'optimisation interagit avec les boucles de référence et ce qu'il faut faire à ce sujet.
Peter Cordes

Réponses:


264

C'est une méthode très facile à utiliser en C ++ 11. Vous devez utiliser à std::chrono::high_resolution_clockpartir de l'en- <chrono>tête.

Utilisez-le comme ceci:

#include <iostream>
#include <chrono>

void function()
{
    long long number = 0;

    for( long long i = 0; i != 2000000; ++i )
    {
       number += 5;
    }
}

int main()
{
    auto t1 = std::chrono::high_resolution_clock::now();
    function();
    auto t2 = std::chrono::high_resolution_clock::now();

    auto duration = std::chrono::duration_cast<std::chrono::microseconds>( t2 - t1 ).count();

    std::cout << duration;
    return 0;
}

Cela mesurera la durée de la fonction.

REMARQUE: vous n'obtiendrez pas toujours le même timing pour une fonction. En effet, le processeur de votre machine peut être moins ou plus utilisé par d'autres processus exécutés sur votre ordinateur, tout comme votre esprit peut être plus ou moins concentré lorsque vous résolvez un exercice de mathématiques. Dans l'esprit humain, nous pouvons nous souvenir de la solution d'un problème mathématique, mais pour un ordinateur, le même processus sera toujours quelque chose de nouveau; ainsi, comme je l'ai dit, vous n'obtiendrez pas toujours le même résultat!


Lorsque j'utilise cette fonction, lors de la première exécution, elle m'a donné 118440535 microsecondes et lors de la deuxième exécution de la même fonction, elle m'a donné 83221031 microsecondes. Les deux mesures de temps ne devraient-elles pas être égales lorsque je mesure uniquement la durée de cette fonction?
Xara

1
Non. Le processeur de votre ordinateur peut être moins ou plus utilisé. Le high_resolution_clockvous donnera le temps physique et réel nécessaire à l'exécution de votre fonction. Ainsi, lors de votre première exécution, votre CPU était moins utilisée que lors de la prochaine exécution. Par «utilisé», je veux dire quel autre travail d'application utilise le processeur.
Victor

1
Oui, si vous avez besoin de la moyenne du temps, c'est un bon moyen de l'obtenir. faites trois essais et calculez la moyenne.
Victor

3
Pourriez-vous s'il vous plaît poster le code sans "utiliser l'espace de noms" en général. Cela permet de voir plus facilement ce qui vient d'où.
Snowman

1
Cela ne devrait-il pas être un steady_clock? N'est-il pas possible que ce high_resolution_clocksoit une horloge non monotone?
Gillespie

15

Voici une fonction qui mesurera le temps d'exécution de toute fonction passée en argument:

#include <chrono>
#include <utility>

typedef std::chrono::high_resolution_clock::time_point TimeVar;

#define duration(a) std::chrono::duration_cast<std::chrono::nanoseconds>(a).count()
#define timeNow() std::chrono::high_resolution_clock::now()

template<typename F, typename... Args>
double funcTime(F func, Args&&... args){
    TimeVar t1=timeNow();
    func(std::forward<Args>(args)...);
    return duration(timeNow()-t1);
}

Exemple d'utilisation:

#include <iostream>
#include <algorithm>

typedef std::string String;

//first test function doing something
int countCharInString(String s, char delim){
    int count=0;
    String::size_type pos = s.find_first_of(delim);
    while ((pos = s.find_first_of(delim, pos)) != String::npos){
        count++;pos++;
    }
    return count;
}

//second test function doing the same thing in different way
int countWithAlgorithm(String s, char delim){
    return std::count(s.begin(),s.end(),delim);
}


int main(){
    std::cout<<"norm: "<<funcTime(countCharInString,"precision=10",'=')<<"\n";
    std::cout<<"algo: "<<funcTime(countWithAlgorithm,"precision=10",'=');
    return 0;
}

Production:

norm: 15555
algo: 2976

2
@ RestlessC0bra: Son implémentation est définie, high_resolution_clockpeut être un alias de system_clock(horloge murale), steady_clockou une troisième horloge indépendante. Voir les détails ici . Pour l'horloge du processeur, std::clockpeut être utilisé
Jahid

2
Deux macros et un typedef global - dont aucun ne protège un seul coup de touche - ne sont certainement rien que j'appellerais élégant.Passer un objet fonction et transmettre parfaitement les arguments séparément est un peu excessif (et même dans le cas de fonctions surchargées peu pratique), lorsque vous pouvez simplement exiger que le code chronométré soit placé dans un lambda. Mais bon, tant que passer des arguments est facultatif.
MikeMB

2
Et c'est une justification pour violer toutes les directives sur la dénomination des macros? Vous ne les préfixez pas, vous n'utilisez pas de majuscules, vous choisissez un nom très courant qui a une forte probabilité de collision avec un symbole local et surtout: pourquoi utilisez-vous une macro (au lieu d'une fonction )? Et pendant que nous y sommes: pourquoi renvoyez-vous la durée sous la forme d'un double représentant les nanosecondes en premier lieu? Nous devrions probablement convenir que nous ne sommes pas d'accord. Mon opinion originale est: "Ce n'est pas ce que j'appellerais du code élégant".
MikeMB

1
Le problème est qu'elles ne sont pas étendues, ce qui m'inquiète, c'est que de telles macros se retrouvent dans un fichier d'en-tête qui est (peut-être indirectement dans le cadre d'une bibliothèque) inclus dans mon code. les noms communs sont utilisés pour les macros, inclus windows.hdans un projet C ++ non trivial. Concernant asserttout d'abord: "quod licet iovi non licet bovi";). Deuxièmement, toutes les décisions prises dans la bibliothèque standard (datant parfois de plusieurs décennies) ne sont pas réellement considérées comme une bonne idée selon les normes modernes. Il y a une raison pour laquelle le concepteur de modules c ++ essaie très fort de ne pas exporter les macros par défaut.
MikeMB

2
@Jahid: Merci. Dans ce cas, considérez mes commentaires nuls et nuls.
MikeMB

9

programme simple pour trouver le temps d'exécution d'une fonction.

#include <iostream>
#include <ctime> // time_t
#include <cstdio>

void function()
{
     for(long int i=0;i<1000000000;i++)
     {
        // do nothing
     }
}

int main()
{

time_t begin,end; // time_t is a datatype to store time values.

time (&begin); // note time before execution
function();
time (&end); // note time after execution

double difference = difftime (end,begin);
printf ("time taken for function() %.2lf seconds.\n", difference );

return 0;
}

6
c'est très inexact, affiche seulement les secondes, mais pas de millisecondes
user25

7

Dans le livre de Scott Meyers, j'ai trouvé un exemple d'expression lambda générique universelle qui peut être utilisée pour mesurer le temps d'exécution d'une fonction. (C ++ 14)

auto timeFuncInvocation = 
    [](auto&& func, auto&&... params) {
        // get time before function invocation
        const auto& start = std::chrono::high_resolution_clock::now();
        // function invocation using perfect forwarding
        std::forward<decltype(func)>(func)(std::forward<decltype(params)>(params)...);
        // get time after function invocation
        const auto& stop = std::chrono::high_resolution_clock::now();
        return stop - start;
     };

Le problème est que vous ne mesurez qu'une seule exécution, les résultats peuvent donc être très différents. Pour obtenir un résultat fiable, vous devez mesurer un grand nombre d'exécutions. D'après la conférence d'Andrei Alexandrescu à la conférence code :: dive 2015 - Writing Fast Code I:

Temps mesuré: tm = t + tq + tn + to

où:

tm - temps mesuré (observé)

t - le temps réel d'intérêt

tq - temps ajouté par le bruit de quantification

tn - temps ajouté par diverses sources de bruit

to - temps de surcharge (mesure, bouclage, fonctions d'appel)

D'après ce qu'il a dit plus tard dans la conférence, vous devriez prendre un minimum de ce grand nombre d'exécutions comme résultat. Je vous encourage à regarder la conférence dans laquelle il explique pourquoi.

Il existe également une très bonne bibliothèque de google - https://github.com/google/benchmark . Cette bibliothèque est très simple à utiliser et puissante. Vous pouvez consulter quelques conférences de Chandler Carruth sur youtube où il utilise cette bibliothèque dans la pratique. Par exemple CppCon 2017: Chandler Carruth «Going Nowhere Faster»;

Exemple d'utilisation:

#include <iostream>
#include <chrono>
#include <vector>
auto timeFuncInvocation = 
    [](auto&& func, auto&&... params) {
        // get time before function invocation
        const auto& start = high_resolution_clock::now();
        // function invocation using perfect forwarding
        for(auto i = 0; i < 100000/*largeNumber*/; ++i) {
            std::forward<decltype(func)>(func)(std::forward<decltype(params)>(params)...);
        }
        // get time after function invocation
        const auto& stop = high_resolution_clock::now();
        return (stop - start)/100000/*largeNumber*/;
     };

void f(std::vector<int>& vec) {
    vec.push_back(1);
}

void f2(std::vector<int>& vec) {
    vec.emplace_back(1);
}
int main()
{
    std::vector<int> vec;
    std::vector<int> vec2;
    std::cout << timeFuncInvocation(f, vec).count() << std::endl;
    std::cout << timeFuncInvocation(f2, vec2).count() << std::endl;
    std::vector<int> vec3;
    vec3.reserve(100000);
    std::vector<int> vec4;
    vec4.reserve(100000);
    std::cout << timeFuncInvocation(f, vec3).count() << std::endl;
    std::cout << timeFuncInvocation(f2, vec4).count() << std::endl;
    return 0;
}

EDIT: Bien sûr, vous devez toujours vous rappeler que votre compilateur peut optimiser quelque chose ou non. Des outils comme perf peuvent être utiles dans de tels cas.


Intéressant - quel est l'avantage d'utiliser un lambda ici par rapport à un modèle de fonction?
user48956

1
La principale différence serait qu'il s'agit d'un objet appelable, mais en effet, vous pouvez obtenir quelque chose de très similaire avec le template variadic et std :: result_of_t.
Krzysztof Sommerfeld

@KrzysztofSommerfeld Comment faire celui-ci pour les méthodes de fonction, lorsque je passe le timing (Object.Method1), il renvoie l'erreur "syntaxe non standard; utilisez '&' pour créer un pointeur vers le membre"
RobinAtTech

timeFuncInvocation ([& objectName] (auto && ... args) {objectName.methodName (std :: forward <decltype (args)> (args) ...);}, arg1, arg2, ...); ou ommit & sign avant objectName (alors vous aurez une copie de l'objet)
Krzysztof Sommerfeld

4

Un moyen simple pour les anciens C ++ ou C:

#include <time.h> // includes clock_t and CLOCKS_PER_SEC

int main() {

    clock_t start, end;

    start = clock();
    // ...code to measure...
    end = clock();

    double duration_sec = double(end-start)/CLOCKS_PER_SEC;
    return 0;
}

La précision du chronométrage en secondes est 1.0/CLOCKS_PER_SEC


1
Ce n'est pas portable. Il mesure le temps processeur sous Linux et le temps d'horloge sous Windows.
BugSquasher

2
  • C'est une méthode très simple à utiliser en C ++ 11.
  • Nous pouvons utiliser std :: chrono :: high_resolution_clock depuis l'en-tête
  • Nous pouvons écrire une méthode pour afficher le temps d'exécution de la méthode sous une forme très lisible.

Par exemple, pour trouver tous les nombres premiers entre 1 et 100 millions, cela prend environ 1 minute et 40 secondes. Ainsi, le temps d'exécution est imprimé comme suit:

Execution Time: 1 Minutes, 40 Seconds, 715 MicroSeconds, 715000 NanoSeconds

Le code est ici:

#include <iostream>
#include <chrono>

using namespace std;
using namespace std::chrono;

typedef high_resolution_clock Clock;
typedef Clock::time_point ClockTime;

void findPrime(long n, string file);
void printExecutionTime(ClockTime start_time, ClockTime end_time);

int main()
{
    long n = long(1E+8);  // N = 100 million

    ClockTime start_time = Clock::now();

    // Write all the prime numbers from 1 to N to the file "prime.txt"
    findPrime(n, "C:\\prime.txt"); 

    ClockTime end_time = Clock::now();

    printExecutionTime(start_time, end_time);
}

void printExecutionTime(ClockTime start_time, ClockTime end_time)
{
    auto execution_time_ns = duration_cast<nanoseconds>(end_time - start_time).count();
    auto execution_time_ms = duration_cast<microseconds>(end_time - start_time).count();
    auto execution_time_sec = duration_cast<seconds>(end_time - start_time).count();
    auto execution_time_min = duration_cast<minutes>(end_time - start_time).count();
    auto execution_time_hour = duration_cast<hours>(end_time - start_time).count();

    cout << "\nExecution Time: ";
    if(execution_time_hour > 0)
    cout << "" << execution_time_hour << " Hours, ";
    if(execution_time_min > 0)
    cout << "" << execution_time_min % 60 << " Minutes, ";
    if(execution_time_sec > 0)
    cout << "" << execution_time_sec % 60 << " Seconds, ";
    if(execution_time_ms > 0)
    cout << "" << execution_time_ms % long(1E+3) << " MicroSeconds, ";
    if(execution_time_ns > 0)
    cout << "" << execution_time_ns % long(1E+6) << " NanoSeconds, ";
}

0

Voici un excellent modèle de classe d'en-tête uniquement pour mesurer le temps écoulé d'une fonction ou de tout bloc de code:

#ifndef EXECUTION_TIMER_H
#define EXECUTION_TIMER_H

template<class Resolution = std::chrono::milliseconds>
class ExecutionTimer {
public:
    using Clock = std::conditional_t<std::chrono::high_resolution_clock::is_steady,
                                     std::chrono::high_resolution_clock,
                                     std::chrono::steady_clock>;
private:
    const Clock::time_point mStart = Clock::now();

public:
    ExecutionTimer() = default;
    ~ExecutionTimer() {
        const auto end = Clock::now();
        std::ostringstream strStream;
        strStream << "Destructor Elapsed: "
                  << std::chrono::duration_cast<Resolution>( end - mStart ).count()
                  << std::endl;
        std::cout << strStream.str() << std::endl;
    }    

    inline void stop() {
        const auto end = Clock::now();
        std::ostringstream strStream;
        strStream << "Stop Elapsed: "
                  << std::chrono::duration_cast<Resolution>(end - mStart).count()
                  << std::endl;
        std::cout << strStream.str() << std::endl;
    }

}; // ExecutionTimer

#endif // EXECUTION_TIMER_H

En voici quelques utilisations:

int main() {
    { // empty scope to display ExecutionTimer's destructor's message
         // displayed in milliseconds
         ExecutionTimer<std::chrono::milliseconds> timer;

         // function or code block here

         timer.stop();

    } 

    { // same as above
        ExecutionTimer<std::chrono::microseconds> timer;

        // code block here...

        timer.stop();
    }

    {  // same as above
       ExecutionTimer<std::chrono::nanoseconds> timer;

       // code block here...

       timer.stop();

    }

    {  // same as above
       ExecutionTimer<std::chrono::seconds> timer;

       // code block here...

       timer.stop();

    }              

    return 0;
}

Puisque la classe est un modèle, nous pouvons spécifier très facilement la façon dont nous voulons que notre temps soit mesuré et affiché. Il s'agit d'un modèle de classe d'utilité très pratique pour faire du benchmarking et est très facile à utiliser.


Personnellement, la stop()fonction membre n'est pas nécessaire car le destructeur arrête le minuteur pour vous.
Casey

@Casey La conception de la classe n'a pas nécessairement besoin de la fonction d'arrêt, mais elle est là pour une raison spécifique. La construction par défaut lors de la création de l'objet avant le test codedémarrage de la minuterie. Ensuite, test codevous utilisez explicitement l'objet timer et appelez sa méthode stop. Vous devez l'invoquer manuellement lorsque vous voulez stopla minuterie. La classe ne prend aucun paramètre. De plus, si vous avez utilisé cette classe comme je l'ai montré, vous verrez qu'il y a un minimum de temps entre l'appel à obj.stopet son destructor.
Francis Cugler

@Casey ... Cela permet également d'avoir plusieurs objets timer dans la même portée, non pas qu'on en aurait vraiment besoin, mais juste une autre option viable.
Francis Cugler

Cet exemple ne peut pas être compilé sous la forme présentée. L'erreur est liée à "aucune correspondance pour l'opérateur << ..."!
Celdor

@Celdor devez-vous vous approprier comprend; comme <chrono>?
Francis Cugler

0

Je recommande d'utiliser steady_clockce qui est garanti pour être monotone, contrairement high_resolution_clock.

#include <iostream>
#include <chrono>

using namespace std;

unsigned int stopwatch()
{
    static auto start_time = chrono::steady_clock::now();

    auto end_time = chrono::steady_clock::now();
    auto delta    = chrono::duration_cast<chrono::microseconds>(end_time - start_time);

    start_time = end_time;

    return delta.count();
}

int main() {
  stopwatch(); //Start stopwatch
  std::cout << "Hello World!\n";
  cout << stopwatch() << endl; //Time to execute last line
  for (int i=0; i<1000000; i++)
      string s = "ASDFAD";
  cout << stopwatch() << endl; //Time to execute for loop
}

Production:

Hello World!
62
163514

0

Vous pouvez avoir une classe simple qui peut être utilisée pour ce type de mesures.

class duration_printer {
public:
    duration_printer() : __start(std::chrono::high_resolution_clock::now()) {}
    ~duration_printer() {
        using namespace std::chrono;
        high_resolution_clock::time_point end = high_resolution_clock::now();
        duration<double> dur = duration_cast<duration<double>>(end - __start);
        std::cout << dur.count() << " seconds" << std::endl;
    }
private:
    std::chrono::high_resolution_clock::time_point __start;
};

La seule chose à faire est de créer un objet dans votre fonction au début de cette fonction

void veryLongExecutingFunction() {
    duration_calculator dc;
    for(int i = 0; i < 100000; ++i) std::cout << "Hello world" << std::endl;
}

int main() {
    veryLongExecutingFunction();
    return 0;
}

et c'est tout. La classe peut être modifiée pour répondre à vos besoins.


0

Comme aucune des réponses fournies n'est très précise ou ne donne des résultats reproductibles, j'ai décidé d'ajouter un lien vers mon code qui a une précision inférieure à la nanoseconde et des statistiques scientifiques.

Notez que cela ne fonctionnera que pour mesurer le code qui prend un temps (très) court à s'exécuter (alias, quelques cycles d'horloge à quelques milliers): s'ils fonctionnent si longtemps qu'ils sont susceptibles d'être interrompus par une interruption -heh- , alors il n'est clairement pas possible de donner un résultat reproductible et précis; la conséquence est que la mesure ne se termine jamais: à savoir, il continue à mesurer jusqu'à ce qu'il soit statistiquement sûr à 99,9% d'avoir la bonne réponse, ce qui ne se produit jamais sur une machine qui a d'autres processus en cours d'exécution lorsque le code prend trop de temps.

https://github.com/CarloWood/cwds/blob/master/benchmark.h#L40


0

Si vous voulez sécuriser le temps et les lignes de code, vous pouvez faire de la mesure du temps d'exécution de la fonction une macro d'une ligne:

a) Implémentez une classe de mesure du temps comme déjà suggéré ci-dessus (voici mon implémentation pour Android):

class MeasureExecutionTime{
private:
    const std::chrono::steady_clock::time_point begin;
    const std::string caller;
public:
    MeasureExecutionTime(const std::string& caller):caller(caller),begin(std::chrono::steady_clock::now()){}
    ~MeasureExecutionTime(){
        const auto duration=std::chrono::steady_clock::now()-begin;
        LOGD("ExecutionTime")<<"For "<<caller<<" is "<<std::chrono::duration_cast<std::chrono::milliseconds>(duration).count()<<"ms";
    }
};

b) Ajoutez une macro pratique qui utilise le nom de la fonction actuelle comme TAG (l'utilisation d'une macro ici est importante, sinon __FUNCTION__évaluera à la MeasureExecutionTimeplace de la fonction que vous voulez mesurer

#ifndef MEASURE_FUNCTION_EXECUTION_TIME
#define MEASURE_FUNCTION_EXECUTION_TIME const MeasureExecutionTime measureExecutionTime(__FUNCTION__);
#endif

c) Écrivez votre macro au début de la fonction que vous souhaitez mesurer. Exemple:

 void DecodeMJPEGtoANativeWindowBuffer(uvc_frame_t* frame_mjpeg,const ANativeWindow_Buffer& nativeWindowBuffer){
        MEASURE_FUNCTION_EXECUTION_TIME
        // Do some time-critical stuff 
}

Ce qui aboutira à la sortie suivante:

ExecutionTime: For DecodeMJPEGtoANativeWindowBuffer is 54ms

Notez que ceci (comme toutes les autres solutions suggérées) mesurera le temps entre le moment où votre fonction a été appelée et son retour, pas nécessairement le moment où votre CPU exécutait la fonction. Cependant, si vous ne donnez aucune modification au planificateur pour suspendre votre code en cours d'exécution en appelant sleep () ou similaire, il n'y a aucune différence entre les deux.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.