|
Lire la fréquence du processeur
29 Mars 2003 (mis à jour le 4 avril 2003)
Par Haypo
Sommaire :
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 chroomètre ultra-pré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 !
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>".
| Exemple de fichier cpuinfo |
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.
Sous Windows, c'est plus complexe : il faut utiliser l'instruction RDTSC
des processeurs compatibles Intel Pentium (sur les autres processeur,
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'attend 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.
Les processeurs compatibles Intel Pentium possède une instructeur permettant
de lire le nombre de cycles processseurs 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" disponnible avec la norme
ISO C99. Mais peu de compilateurs la supporte. Le seul que je connais
est GCC (http://gcc.gnu.org).
- Emuler 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").
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 frequence (format double)
- Sauver le compteur RDTSC dans une variable avant (format double)
- Faire les calculs ...
- Sauver le compteur RDTSC dans une variable apres (format double)
- Calculer la différence de temps duree =
(apres - avant) / frequence
Voilà pour l'algorithme, en pratique : voyez le code source donné
plus bas :-)
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.
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 !!!
Télécharger
le code assembleur
Revenir sur la page de Haypo
|