Mesure du temps d'exécution des programmes en C++

Introduction


C'est une question récurrente sur les forums de programmation : comment faire pour mesurer le temps d'exécution d'un programme avec une résolution meilleure que celle de la seconde ? Pour avoir cherché pendant pas mal de temps, je sais que les réponses sont souvent complexes, du moins en apparence. Il est aussi très répandu de faire appel à des bibliothèques annexes. Pourtant la solution est simple, très simple même...

Les méthodes présentées dans la suite ne sont pas les plus optimisées et ne vous permettront pas d'aller à la chasse aux cycles d'horloges. Cependant, elles présentent l'avantage d'être relativement simples à mettre en place et sont suffisamment génériques pour être implémentables un peu partout. De plus, comme elles se basent sur l'heure renvoyée par le système, elles permettent d'une part de mesurer le temps d'exécution effectivement "vécu" par l'utilisateur et d'autre part d'éviter les problèmes qui peuvent survenir dans les programmes parallélisés avec des méthodes basées sur des ticks cpu.

Remarque :
Dans tous les exemples qui suivent, pour afficher le temps d'exécution en secondes, il suffit de faire un :
std::cout << "Execution time : " << texec ;



La méthode basique avec ctime


Description :
La librairie standard du C, et plus particulièrement <ctime> (ou time.h) fournit une fonction simple pour récupérer le temps : time(NULL) qui renvoie l'heure sous la forme d'une variable de type time_t. En mesurant l'heure au début et à la fin du programme, et en faisant la différence entre les deux avec un difftime(tend,tbegin) (qui renvoie sous la forme d'un double tend-tbegin en secondes), on récupère le temps approximatif d'exécution à la seconde près. C'est la méthode basique, portable, passe partout ... mais limitée à la seconde.

Exemple d'implémentation :
// Include
#include <iostream>
#include <ctime>

// Program
int main()
{
    // Variables
    time_t tbegin,tend;
    double texec=0.;

    // Start timer
    tbegin=time(NULL);				// get the current calendar time

    /* YOUR PROGRAM HERE */
    /* YOUR PROGRAM HERE */
    /* YOUR PROGRAM HERE */

    // End timer
    tend=time(NULL);				// get the current calendar time

	// Compute execution time
    texec=difftime(tend,tbegin);	// tend-tbegin (result in second)

    // Return
    return 0;
}

Liens :



Sous Windows avec ::GetSystemTimeAsFileTime


Description :
Sous Windows, le header <windows.h> permet de mettre en place la fonction ::GetSystemTimeAsFileTime, qui permet de renvoyer le temps écoulé en centaines de nanosecondes depuis une date de référence. On peut alors récupérer ce temps dans une structure de deux DWORD (int32) avec HighPart et LowPart et le convertir le moment voulu en int64 avec QuadPart. Au final, on peut obtenir le temps d'exécution en reconvertissant en secondes le résultat obtenu en centaines de nanosecondes. Il est à noté que même si l'unité utilisée est la centaine de nanoseconde, la précision du timer se situe plutôt entre 1 et 10 millisecondes.

Exemple d'implémentation :
// Include
#include <iostream>
#include <windows.h>

// Program
int main()
{
    // Variables
    ULARGE_INTEGER tbegin,tend;
    FILETIME ttmp={0,0};                    // temporary variable
    double texec=0.;

    // Start timer
    ::GetSystemTimeAsFileTime(&ttmp);       // store current time in ttmp structure
    tbegin.HighPart=ttmp.dwHighDateTime;    // convert ttmp to two int32
    tbegin.LowPart=ttmp.dwLowDateTime;

    /* YOUR PROGRAM HERE */
    /* YOUR PROGRAM HERE */
    /* YOUR PROGRAM HERE */

    // End timer
    ::GetSystemTimeAsFileTime(&ttmp);       // store current time in ttmp structure
    tend.HighPart=ttmp.dwHighDateTime;      // convert ttmp to two int32
    tend.LowPart=ttmp.dwLowDateTime;

    // Compute execution time
    texec=((double)((tend.QuadPart-tbegin.QuadPart)/10000))/1000.;

    // Return
    return 0;
}

Liens :



Sous Linux avec gettimeofday


Description :
Sous Linux, le header <sys/time.h> propose la fonction gettimeofday grâce à laquelle on peut récupérer le temps écoulé depuis une date de référence dans une structure timeval composée de tv_sec (secondes) et de tv_usec (microsecondes) (à ne pas confondre avec timespec composée de tv_sec et de tv_nsec). Comme précédemment, en faisant la différence entre les deux, on récupère le temps d'exécution du programme, avec la conversion d'unités adéquate.

Remarque :
Je n'ai pas testé sur Mac mais cette méthode doit fonctionner aussi.

Exemple d'implémentation :
// Include
#include <iostream>
#include <sys/time.h>

// Program
int main()
{
    // Variables
    struct timeval tbegin,tend;
    double texec=0.;

    // Start timer
    gettimeofday(&tbegin,NULL);

    /* YOUR PROGRAM HERE */
    /* YOUR PROGRAM HERE */
    /* YOUR PROGRAM HERE */

    // End timer
    gettimeofday(&tend,NULL);

    // Compute execution time
    texec=((double)(1000*(tend.tv_sec-tbegin.tv_sec)+((tend.tv_usec-tbegin.tv_usec)/1000)))/1000.;

    // Return
    return 0;
}

Liens :



Conclusion


J'espère que cela vous aura semblé assez simple pour être implémentable très rapidement pour tester votre code. Certes, vous n'aurez pas une résolution temporelle de l'ordre de la microseconde, et certains cas pourront peut être poser problème (un changement d'heure par exemple), mais le but ici était de livrer une méthode "clé en main" facilement compréhensible. Vous n'aurez donc plus aucune excuse pour vous limiter à un timer avec une résolution d'une seconde !

En cadeau, une implémentation complète et portable :
// Select your os here
//#define OS_GENERIC
//#define OS_WINDOWS
#define OS_LINUX

// Include
#include <iostream>
#ifdef OS_GENERIC
    #include <ctime>
#endif
#ifdef OS_WINDOWS
    #include <windows.h>
#endif
#ifdef OS_LINUX
    #include <sys/time.h>
#endif

// Program
int main()
{
    // Start timer
    double texec=0.;
    #ifdef OS_GENERIC
    time_t tbegin=time(NULL);
    #endif
    #ifdef OS_WINDOWS
    FILETIME ttmp={0,0};
    ULARGE_INTEGER tbegin,tend;
    ::GetSystemTimeAsFileTime(&ttmp);
    tbegin.HighPart=ttmp.dwHighDateTime;
    tbegin.LowPart=ttmp.dwLowDateTime;
    #endif
    #ifdef OS_LINUX
    struct timeval tbegin,tend;
    gettimeofday(&tbegin,NULL);
    #endif

    /* YOUR PROGRAM HERE */
    /* YOUR PROGRAM HERE */
    /* YOUR PROGRAM HERE */

    // End timer
    #ifdef OS_GENERIC
    texec=difftime(time(NULL),tbegin);
    #endif
    #ifdef OS_WINDOWS
    ::GetSystemTimeAsFileTime(&ttmp);
    tend.HighPart=ttmp.dwHighDateTime;
    tend.LowPart=ttmp.dwLowDateTime;
    texec=((double)((tend.QuadPart-tbegin.QuadPart)/10000))/1000.;
    #endif
    #ifdef OS_LINUX
    gettimeofday(&tend,NULL);
    texec=((double)(1000*(tend.tv_sec-tbegin.tv_sec)+((tend.tv_usec-tbegin.tv_usec)/1000)))/1000.;
    #endif

    // Return
    return 0;
}

Para obter informações mais completas sobre otimizações do compilador, consulte nosso aviso de otimização.