I. Introduction ♪▲
Je vais tenter de vous expliquer comment lire la fréquence du processeur, accéder à l'instruction RDTSC (Read Timestamp Counter), et créer un chronomètre ultraprécis sous Linux ou Windows. Ce code ne fonctionne que sur les processeurs « compatibles Intel Pentium » à cause de l'instruction RDTSC. Ne vous inquiétez pas votre (et mon) AMD Athlon la supporte parfaitement ;-)
Ne perdez pas votre temps à copier/coller le code source, un code complet en C est donné à la fin de cette page.
Les extraits de codes pour Linux fonctionnent avec GCC, les extraits pour Windows fonctionnent avec Borland C++ Builder. Le code à télécharger fonctionne également sous Visual C++. Ce dernier nécessite des bouts de code pour gérer les nombres en 64 bits, ce qui complique l'article.
Si vous avez des suggestions et des questions : contactez-moi !
II. Lecture de la fréquence du CPU sous Linux▲
Sous Linux, c'est facile, car le noyau crée un fichier (/proc/cpuinfo) contenant la fréquence du processeur. La ligne utile est « cpu MHz : <fréquence> ».
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 7
model name : Pentium III (Katmai)
stepping : 3
cpu MHz : 451.030
cache size : 512 KB
fdiv_bug : no
hlt_bug : no
f00f_bug : no
coma_bug : no
fpu : yes
fpu_exception : yes
cpuid level : 2
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 sep mtrr pge mca cmov pat pse36 mmx fxsr sse
bogomips : 897.84
On voit ici que c'est un processeur Intel Pentium III ayant une fréquence de 450 Mhz (la fréquence calculée est de 451.030 MHz).
Le texte (« cpu MHz ») et la valeur sont séparés par des espaces et/ou des tabulations, un double-point (« : »), puis un espace ou une tabulation.
Ce qui va nous donner le code suivant :
#define NOMFICH_CPUINFO "/proc/cpuinfo"
// Lit la fréquence du processeur
// Renvoie 0 en cas d'échec
int
LitFrequenceCpu (
double
*
frequence)
{
const
char
*
prefixe_cpu_mhz =
"
cpu MHz
"
;
FILE*
F;
char
ligne[300
+
1
];
char
*
pos;
int
ok=
0
;
// Ouvre le fichier
F =
fopen
(
NOMFICH_CPUINFO, "
r
"
);
if
(!
F) return
0
;
// Lit une ligne apres l'autre
while
(!
feof
(
F))
{
// Lit une ligne de texte
fgets (
ligne, sizeof
(
ligne), F);
// C'est la ligne contenant la frequence?
if
(!
strncmp
(
ligne, prefixe_cpu_mhz, strlen
(
prefixe_cpu_mhz)))
{
// Oui, alors lit la frequence
pos =
strrchr (
ligne, '
:
'
) +
2
;
if
(!
pos) break
;
if
(
pos[strlen
(
pos)-
1
] ==
'
\n
'
) pos[strlen
(
pos)-
1
] =
'
\0
'
;
strcpy (
ligne, pos);
strcat (
ligne,"
e6
"
);
*
frequence =
atof (
ligne);
ok =
1
;
break
;
}
}
fclose (
F);
return
ok;
}
J'ai choisi le format double, car il fonctionne avec tous les compilateurs et a une précision suffisante.
III. Lire la fréquence du CPU sous Windows▲
Sous Windows, c'est plus complexe : il faut utiliser l'instruction RDTSC des processeurs compatibles Intel Pentium (sur les autres processeurs, je ne sais pas comment faire), et les fonctions QueryPerformanceFrequency et QueryPerformanceCounter.
C'est un problème physique assez simple : on a deux compteurs qu'on va appeler WIN (compteur Windows) et CPU (compteur processeur RDTSC). On sait mesure une durée avec les deux compteurs :
- Twin est la différence de temps du compteur WIN, mesurée avec deux appels à QueryPerformanceCounter ;
- Tcpu est la différence de temps du compteur CPU, mesurée avec deux appels à RDTSC.
Par contre, on ne connait que la fréquence du compteur WIN, donnée par la fonction QueryPerformanceFrequency, qu'on va noter Fwin. Pour rappel, une fréquence est un nombre d'impulsions exprimé en Hertz (Hz) ou « par seconde » (s^-1). On notera la fréquence du compteur CPU : Fcpu.
Donc, pour une pause d'exactement Texact secondes, on a donc les équations :
- Texact =Twin / Fwin ;
- Texact = Tcpu / Fcpu ;
- => Twin / Fwin = Tcpu / Fcpu ;
- Enfin : Fcpu = (Tcpu * Fwin ) / Twin.
On notera que la durée de la pause Texact n'entre plus en compte dans le calcul… en théorie. Mais en pratique, plus la pause est longue meilleure est la précision. J'ai retenu Texact = 500 ms, car c'est le meilleur compromis entre précision et durée de la pause (= blocage du programme).
Dans le code donné à la fin du programme, j'utilise encore une autre technique : j'attends une certaine durée en lisant sans arrêt le chronomètre Windows. Ceci permet d'avoir des pauses très courtes, tout en gardant une précision correcte.
Ce qui donne le code :
// Lit la fréquence du processeur
// Renvoie la fréquence en Hz dans 'frequence' si le code de retour est
// différent de 1. Renvoie 0 en cas d'erreur.
int
LitFrequenceCpu (
double
*
frequence)
{
unsigned
__int64 Fwin;
unsigned
__int64 Twin_avant, Twin_apres;
double
Tcpu_avant, Tcpu_apres;
double
Fcpu;
// Lit la frequence du chronometre Windows
if
(!
QueryPerformanceFrequency
((
LARGE_INTEGER*
)&
Fwin)) return
0
;
printf (
"
Frequence du compteur Windows =
"
);
AfficheFrequence (
Fwin);
// Avant
Tcpu_avant =
RDTSC
(
);
QueryPerformanceCounter
((
LARGE_INTEGER*
)&
Twin_avant);
// Pause de 500 ms
Sleep
(
500
);
// Apres
Tcpu_apres =
RDTSC
(
);
QueryPerformanceCounter
((
LARGE_INTEGER*
)&
Twin_apres);
// Calcule la fréquence en MHz
Fcpu =
(
Tcpu_apres -
Tcpu_avant);
Fcpu *=
Fwin;
Fcpu /=
(
double
)(
Twin_apres -
Twin_avant);
*
frequence =
Fcpu;
return
1
;
}
Même remarque que pour le code Linux : J'ai choisi le format double, car il fonctionne avec tous les compilateurs et a une précision suffisante.
IV. L'instruction RDTSC▲
Les processeurs compatibles Intel Pentium possèdent un instructeur permettant de lire le nombre de cycles processeur depuis l'allumage de l'ordinateur. C'est donc un nombre non signé sur 64 bits que l'on va lire. Il nous faut donc une variable pouvant stocker un tel nombre. Plusieurs solutions s'offrent à nous :
- utiliser le format « unsigned long long » disponible avec la norme ISO C99. Mais peu de compilateurs la supportent. Le seul que je connais est GCC (http://gcc.gnu.org) ;
- émuler un nombre 64 bits en stocker deux nombres de 32 bits (au format « unsigned long »). Ceci fonctionne la totalité des compilateurs C (ou bien ?) ;
- utiliser le format « double »qui a une taille et une précision suffisante.
L'instruction en question est « RDTSC » (Read Timestamp Counter), ou en code machine « db 0x0F, 0x31 ».
Ce qui nous donne le code suivant :
// Instruction RDTSC du processeur Pentium
double
RDTSC
(
void
)
{
#ifdef linux
unsigned
long
long
x;
__asm__ volatile
(
"
.byte 0x0f, 0x31
"
: "
=A
"
(
x));
return
(
double
)x;
#else
unsigned
long
a, b;
double
x;
asm
{
db 0x0F
,0x31
mov [a],eax
mov [b],edx
}
x =
b;
x *=
4294967296
;
x +=
a;
return
x;
#endif
}
Ce code fonctionne à la fois sous Linux et sous Windows (grâce à l'utilisation du « #ifdef linux »).
V. Chronomètre ultraprécis▲
Maintenant que nous avons les deux outils nécessaires : fréquence du processeur et instruction RDTSC, on peut lire une durée très précisément. Pour cela, il faut :
- lire la fréquence du processeur fréquence (format double) ;
- sauver le compteur RDTSC dans une variable avant (format double) ;
- faire les calculs … ;
- sauver le compteur RDTSC dans une variable après (format double) ;
- calculer la différence de temps durée = (après - avant) / fréquence.
Voilà pour l'algorithme, en pratique : voyez le code source donné plus bas :-)
VI. Code complet en C▲
Plutôt que de copier/coller inutilement, je vous propose de charger un exemple complet qui fonctionne simultanément sous Linux et sous Windows.
Télécharger le code C complet (6 Ko)
Attention, le code est sous licence GPL !
Ce code source fonctionne sous Linux avec GCC 2.95, Borland C++ Builder 5 (Windows) et Visual C++ 6 (Windows). Bien sûr, il doit être compatible avec d'autres compilateurs moyennant une petite correction du code.
VII. Code en assembleur▲
Jean-Paul MICHEL m'a envoyé un code source en assembleur pour lire la fréquence du processeur en assembleur. Il l'offre gratos, trop sympa ! Par contre, je vous prierai de respecter le copyright !!!