2016-04-16 1 views
0

J'ai confusion dans cette ligne particulière ->Mesure du temps d'exécution du programme avec cycle Compteurs

result = (double) hi * (1 << 30) * 4 + lo; 

du code ci-dessous:

void access_counter(unsigned *hi, unsigned *lo) 
// Set *hi and *lo to the high and low order bits of the cycle 
// counter. 
{ 
    asm("rdtscp; movl %%edx,%0; movl %%eax,%1" // Read cycle counter 
     : "=r" (*hi), "=r" (*lo)    // and move results to 
     : /* No input */      // the two outputs 
     : "%edx", "%eax"); 
} 

double get_counter() 
// Return the number of cycles since the last call to start_counter. 
{ 
    unsigned ncyc_hi, ncyc_lo; 
    unsigned hi, lo, borrow; 
    double result; 

    /* Get cycle counter */ 
    access_counter(&ncyc_hi, &ncyc_lo); 
    lo = ncyc_lo - cyc_lo; 
    borrow = lo > ncyc_lo; 
    hi = ncyc_hi - cyc_hi - borrow; 
    result = (double) hi * (1 << 30) * 4 + lo; 
    if (result < 0) { 
    fprintf(stderr, "Error: counter returns neg value: %.0f\n", result); 
    } 
    return result; 
} 

La chose que je ne comprends pas est que pourquoi salut être multiplié par 2^30 puis 4? et puis faible ajouté à cela? Quelqu'un s'il vous plaît expliquer ce qui se passe dans cette ligne de code. Je sais que ce haut et bas contiennent.

+0

Avez-vous regardé les documents pour rdtscp? Il renvoie un nombre de 64 bits. Les 32 bits inférieurs dans eax et les 32 bits supérieurs dans edx. Dans une implémentation judicieuse, access_counter renvoie un entier de 64 bits. Pourquoi cela est transformé en un point flottant que je ne peux pas imaginer. –

+0

Et pendant que j'y suis, que asm est incorrectement écrit. 1) Il modifie ecx sans informer le compilateur via la sortie ou le clobber (très mauvais). 2) Il a 2 relevés mov inutiles (gaspillage de temps et de précieux registres). Que diriez-vous de 'non signé int a; unsigned long long b = __builtin_ia32_rdtscp (&a); '? Si vous avez utilisé un nombre de 64 bits pour (apparemment non défini?) cyc_lo & cyc_hi, cela facilite aussi la soustraction de newtime - oldtime. –

Répondre

1

La réponse courte:

Cette ligne devient un nombre entier de 64 bits qui est stocké sous forme de 2 valeurs de 32 bits en un nombre à virgule flottante.

Pourquoi le code n'utilise-t-il pas simplement un entier 64 bits? Eh bien, gcc a supporté des nombres de 64 bits pendant longtemps, mais probablement ce code est antérieur à cela. Dans ce cas, la seule façon de prendre en charge des nombres aussi gros est de les mettre dans un nombre à virgule flottante.

La réponse longue:

Tout d'abord, vous devez comprendre comment fonctionne rdtscp. Lorsque cette instruction assembleur est invoquée, elle fait 2 choses:

1) Définit ecx sur IA32_TSC_AUX MSR. D'après mon expérience, cela signifie généralement que l'ecx est mise à zéro. 2) Définit edx: eax à la valeur actuelle du compteur d'horodatage du processeur. Cela signifie que les 64 bits inférieurs du compteur vont dans eax, et les 32 bits supérieurs sont dans edx.

Dans cet esprit, regardons le code. Lorsqu'il est appelé depuis get_counter, access_counter va mettre edx dans 'ncyc_hi' et eax dans 'ncyc_lo'. Puis get_counter va faire:

lo = ncyc_lo - cyc_lo; 
borrow = lo > ncyc_lo; 
hi = ncyc_hi - cyc_hi - borrow; 

Qu'est-ce que cela fait? Comme le temps est stocké dans 2 numéros 32bit différents, si nous voulons savoir combien de temps s'est écoulé, nous devons faire un peu de travail pour trouver la différence entre l'ancien et le nouveau. Quand c'est fait, le résultat est stocké (encore, en utilisant 2 nombres de 32bit) dans hi/lo.

Ce qui nous amène finalement à votre question.

result = (double) hi * (1 << 30) * 4 + lo; 

Si nous pouvions utiliser des entiers de 64 bits, conversion 2 valeurs 32 bits à une seule valeur de 64 bits ressemblerait à ceci:

unsigned long long result = hi; // put hi into the 64bit number. 
result <<= 32;     // shift the 32 bits to the upper part of the number 
results |= low;     // add in the lower 32bits. 

Si vous n'êtes pas habitué à un décalage de bits, la recherche peut-être ça comme CA aidera. Si lo = 1 et élevé = 2, exprimé en nombres hexadécimaux:

result = hi; 0x0000000000000002 
result <<= 32; 0x0000000200000000 
result |= low; 0x0000000200000001 

Mais si nous supposons que le compilateur ne supporte pas les entiers de 64 bits, cela ne fonctionnera pas. Bien que les nombres à virgule flottante puissent contenir des valeurs aussi grandes, ils ne prennent pas en charge le décalage. Nous avons donc besoin de trouver un moyen de déplacer 'salut' laissé par 32 bits, sans en utilisant le décalage à gauche. Ok alors, le décalage à gauche de 1 est vraiment le même que de multiplier par 2.Le décalage à gauche de 2 équivaut à la multiplication par 4. Le décalage à gauche de [omis ...] Le décalage à gauche de 32 revient à multiplier par 4 294 967 296.

Par une coïncidence étonnante, 4294967296 == (1 < < 30) * 4.

Alors, pourquoi écrire de cette façon compliquée? Bien, 4,294,967,296 est un assez grand nombre. En fait, il est trop grand pour tenir dans un entier 32 bits. Ce qui signifie que si nous le mettons dans notre code source, un compilateur qui ne supporte pas les entiers 64 bits peut avoir du mal à trouver comment le traiter. Écrit comme ceci, le compilateur peut générer toutes les instructions à virgule flottante dont il a besoin pour travailler sur ce très grand nombre.

Pourquoi le code actuel est erroné:

Il ressemble à des variations de ce code ont erré autour de l'Internet depuis longtemps. A l'origine (je suppose) access_counter a été écrit en utilisant rdtsc au lieu de rdtscp. Je ne vais pas essayer de décrire la différence entre les deux (google les), sauf pour signaler que rdtsc ne définit pas ecx, et rdtscp fait. Celui qui a changé rdtsc en rdtscp ne le savait apparemment pas, et n'a pas réussi à ajuster le contenu de l'assembleur en ligne pour le refléter. Alors que votre code pourrait bien fonctionner malgré cela, il pourrait faire quelque chose de bizarre à la place. Pour résoudre ce problème, vous pouvez faire:

asm("rdtscp; movl %%edx,%0; movl %%eax,%1" // Read cycle counter 
    : "=r" (*hi), "=r" (*lo)     // and move results to 
    : /* No input */       // the two outputs 
    : "%edx", "%eax", "%ecx"); 

Bien que cela fonctionne, ce n'est pas optimal. Les registres sont une ressource précieuse et rare sur i386. Ce minuscule fragment en utilise 5. Avec une légère modification:

asm("rdtscp" // Read cycle counter 
    : "=d" (*hi), "=a" (*lo) 
    : /* No input */ 
    : "%ecx"); 

Maintenant nous avons 2 instructions d'assemblage de moins, et nous utilisons seulement 3 registres.

Mais même ce n'est pas le meilleur que nous pouvons faire. Dans le (probablement assez long) puisque ce code a été écrit, gcc a ajouté à la fois un soutien pour les entiers de 64 bits et une fonction pour lire le tsc, de sorte que vous n'avez pas besoin d'utiliser asm du tout:

unsigned int a; 
unsigned long long result; 

result = __builtin_ia32_rdtscp(&a); 

« un 'est la valeur (inutile?) qui était renvoyée dans ecx. L'appel de la fonction l'exige, mais nous pouvons simplement ignorer la valeur renvoyée.

Ainsi, au lieu de faire quelque chose comme ça (que je suppose que votre code existant ne):

unsigned cyc_hi, cyc_lo; 

access_counter(&cyc_hi, &cyc_lo); 
// do something 
double elapsed_time = get_counter(); // Find the difference between cyc_hi, cyc_lo and the current time 

Nous pouvons faire:

unsigned int a; 
unsigned long long before, after; 

before = __builtin_ia32_rdtscp(&a); 
// do something 
after = __builtin_ia32_rdtscp(&a); 
unsigned long long elapsed_time = after - before; 

Ceci est plus courte, n'utilise pas Hard- Comprendre l'assembleur, est plus facile à lire, à maintenir et à produire le meilleur code possible.

Mais cela nécessite une version relativement récente de gcc.