Cette nouvelle réponse utilise la fonctionnalité de C ++ 11 <chrono>
. Bien qu'il existe d'autres réponses qui montrent comment utiliser <chrono>
, aucune d'elles ne montre comment utiliser <chrono>
avec l' RDTSC
installation mentionnée dans plusieurs des autres réponses ici. J'ai donc pensé montrer comment utiliser RDTSC
avec <chrono>
. De plus, je vais vous montrer comment vous pouvez modéliser le code de test sur l'horloge afin de pouvoir basculer rapidement entre RDTSC
les fonctions d'horloge intégrées de votre système (qui seront probablement basées sur clock()
, clock_gettime()
et / ou QueryPerformanceCounter
.
Notez que l' RDTSC
instruction est spécifique à x86. QueryPerformanceCounter
est Windows uniquement. Et clock_gettime()
c'est POSIX uniquement. Ci-dessous, j'introduis deux nouvelles horloges: std::chrono::high_resolution_clock
et std::chrono::system_clock
, qui, si vous pouvez supposer C ++ 11, sont maintenant multiplateformes.
Tout d'abord, voici comment créer une horloge compatible C ++ 11 à partir des rdtsc
instructions d'assemblage Intel . Je l'appellerai x::clock
:
#include <chrono>
namespace x
{
struct clock
{
typedef unsigned long long rep;
typedef std::ratio<1, 2'800'000'000> period; // My machine is 2.8 GHz
typedef std::chrono::duration<rep, period> duration;
typedef std::chrono::time_point<clock> time_point;
static const bool is_steady = true;
static time_point now() noexcept
{
unsigned lo, hi;
asm volatile("rdtsc" : "=a" (lo), "=d" (hi));
return time_point(duration(static_cast<rep>(hi) << 32 | lo));
}
};
} // x
Tout ce que cette horloge fait est de compter les cycles CPU et de les stocker dans un entier 64 bits non signé. Vous devrez peut-être modifier la syntaxe du langage d'assemblage pour votre compilateur. Ou votre compilateur peut offrir un intrinsèque que vous pouvez utiliser à la place (par exemple now() {return __rdtsc();}
).
Pour construire une horloge, vous devez lui donner la représentation (type de stockage). Vous devez également fournir la période d'horloge, qui doit être une constante de temps de compilation, même si votre machine peut changer la vitesse d'horloge dans différents modes d'alimentation. Et à partir de ceux-ci, vous pouvez facilement définir la durée et le point temporel "natifs" de votre horloge en fonction de ces principes fondamentaux.
Si tout ce que vous voulez faire est de sortir le nombre de coups d'horloge, peu importe le nombre que vous donnez pour la période d'horloge. Cette constante n'entre en jeu que si vous souhaitez convertir le nombre de tics d'horloge en une unité en temps réel telle que les nanosecondes. Et dans ce cas, plus vous êtes en mesure de fournir la vitesse d'horloge avec précision, plus la conversion en nanosecondes sera précise (millisecondes, peu importe).
Voici un exemple de code qui montre comment utiliser x::clock
. En fait, j'ai modelé le code sur l'horloge car j'aimerais montrer comment vous pouvez utiliser de nombreuses horloges différentes avec exactement la même syntaxe. Ce test particulier montre quelle est la surcharge de la boucle lors de l'exécution de ce que vous voulez chronométrer sous une boucle:
#include <iostream>
template <class clock>
void
test_empty_loop()
{
// Define real time units
typedef std::chrono::duration<unsigned long long, std::pico> picoseconds;
// or:
// typedef std::chrono::nanoseconds nanoseconds;
// Define double-based unit of clock tick
typedef std::chrono::duration<double, typename clock::period> Cycle;
using std::chrono::duration_cast;
const int N = 100000000;
// Do it
auto t0 = clock::now();
for (int j = 0; j < N; ++j)
asm volatile("");
auto t1 = clock::now();
// Get the clock ticks per iteration
auto ticks_per_iter = Cycle(t1-t0)/N;
std::cout << ticks_per_iter.count() << " clock ticks per iteration\n";
// Convert to real time units
std::cout << duration_cast<picoseconds>(ticks_per_iter).count()
<< "ps per iteration\n";
}
La première chose que fait ce code est de créer une unité "en temps réel" pour afficher les résultats. J'ai choisi les picosecondes, mais vous pouvez choisir toutes les unités que vous aimez, en virgule flottante ou intégrale. À titre d'exemple, il y a une std::chrono::nanoseconds
unité préfabriquée que j'aurais pu utiliser.
Comme autre exemple, je veux imprimer le nombre moyen de cycles d'horloge par itération sous forme de virgule flottante, donc je crée une autre durée, basée sur le double, qui a les mêmes unités que le tick de l'horloge (appelé Cycle
dans le code).
La boucle est chronométrée avec des appels de clock::now()
chaque côté. Si vous souhaitez nommer le type renvoyé par cette fonction, c'est:
typename clock::time_point t0 = clock::now();
(comme clairement montré dans l' x::clock
exemple, et cela est également vrai pour les horloges fournies par le système).
Pour obtenir une durée en termes de ticks d'horloge en virgule flottante, on soustrait simplement les deux points temporels, et pour obtenir la valeur par itération, divisez cette durée par le nombre d'itérations.
Vous pouvez obtenir le décompte dans n'importe quelle durée à l'aide de la count()
fonction membre. Cela renvoie la représentation interne. Enfin, j'utilise std::chrono::duration_cast
pour convertir la durée Cycle
en durée picoseconds
et l'imprimer.
Pour utiliser ce code, c'est simple:
int main()
{
std::cout << "\nUsing rdtsc:\n";
test_empty_loop<x::clock>();
std::cout << "\nUsing std::chrono::high_resolution_clock:\n";
test_empty_loop<std::chrono::high_resolution_clock>();
std::cout << "\nUsing std::chrono::system_clock:\n";
test_empty_loop<std::chrono::system_clock>();
}
Ci-dessus, j'exerce le test en utilisant notre produit fait maison x::clock
et je compare ces résultats avec deux des horloges fournies par le système: std::chrono::high_resolution_clock
et std::chrono::system_clock
. Pour moi, ceci s'imprime:
Using rdtsc:
1.72632 clock ticks per iteration
616ps per iteration
Using std::chrono::high_resolution_clock:
0.620105 clock ticks per iteration
620ps per iteration
Using std::chrono::system_clock:
0.00062457 clock ticks per iteration
624ps per iteration
Cela montre que chacune de ces horloges a une période de ticks différente, car les ticks par itération sont très différents pour chaque horloge. Cependant, une fois converti en une unité de temps connue (par exemple, picosecondes), j'obtiens approximativement le même résultat pour chaque horloge (votre kilométrage peut varier).
Notez que mon code est totalement exempt de "constantes de conversion magiques". En effet, il n'y a que deux nombres magiques dans tout l'exemple:
- La vitesse d'horloge de ma machine afin de la définir
x::clock
.
- Le nombre d'itérations à tester. Si la modification de ce nombre fait varier considérablement vos résultats, vous devriez probablement augmenter le nombre d'itérations ou vider votre ordinateur des processus concurrents pendant le test.