Question

Le morceau de code suivant nous a été donnée de notre instructeur afin que nous puissions mesurer certaines performances des algorithmes:

#include <stdio.h>
#include <unistd.h>

static unsigned cyc_hi = 0, cyc_lo = 0;

static void access_counter(unsigned *hi, unsigned *lo) {
    asm("rdtsc; movl %%edx,%0; movl %%eax,%1"
    : "=r" (*hi), "=r" (*lo)
    : /* No input */
    : "%edx", "%eax");
}

void start_counter() {
    access_counter(&cyc_hi, &cyc_lo);
}

double get_counter() {
    unsigned ncyc_hi, ncyc_lo, hi, lo, borrow;
    double result;

    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;

    return result;
}

Cependant, je besoin de ce code pour être portable aux machines avec différentes fréquences CPU. Pour cela, je suis en train de calculer la fréquence CPU de la machine où le code est exécuté comme ceci:

int main(void)
{
    double c1, c2;

    start_counter();

    c1 = get_counter();
    sleep(1);
    c2 = get_counter();

    printf("CPU Frequency: %.1f MHz\n", (c2-c1)/1E6);
    printf("CPU Frequency: %.1f GHz\n", (c2-c1)/1E9);

    return 0;
}

Le problème est que le résultat est toujours 0 et je ne peux pas comprendre pourquoi. Je suis sous Linux (Arch) en tant qu'invité sur VMware.

Sur la machine d'un ami (MacBook), il travaille dans une certaine mesure; Je veux dire, le résultat est plus grand que 0, mais il est la variable car la fréquence du processeur ne fixe (nous avons essayé de le réparer, mais pour une raison quelconque, nous ne sommes pas en mesure de le faire). Il a une autre machine qui exécute Linux (Ubuntu) comme hôte et il rend compte également 0. Cela exclut le problème étant sur la machine virtuelle, que je pensais que c'était la question au début.

Toutes les idées pourquoi cela arrive et comment puis-je résoudre ce problème?

Était-ce utile?

La solution

D'accord, puisque l'autre réponse n'a pas été utile, je vais essayer d'expliquer plus en détail sur. Le problème est qu'une unité centrale de traitement moderne peut exécuter des instructions de commande. Votre code commence comme quelque chose comme:

rdtsc
push 1
call sleep
rdtsc

Unités centrales modernes font pas nécessairement exécuter des instructions dans leur ordre d'origine bien. En dépit de votre commande initiale, la CPU est (la plupart du temps) libre d'exécuter que comme:

rdtsc
rdtsc
push 1
call sleep

Dans ce cas, il est clair pourquoi la différence entre les deux rdtscs serait (au moins très proche) 0. Pour éviter cela, vous devez exécuter une instruction que la CPU jamais réorganiser d'exécuter hors d'usage. L'instruction la plus commune à utiliser pour c'est CPUID. L'autre réponse que je mixte devrait (si ma mémoire est bonne) commencer à peu près à partir de là, sur les mesures nécessaires pour utiliser correctement / efficacement CPUID pour cette tâche.

Bien sûr, il est possible que Tim Poste avait raison, et vous êtes aussi voir les problèmes en raison d'une machine virtuelle. Néanmoins, comme il est actuellement, il n'y a aucune garantie que votre code fonctionne correctement même sur le matériel réel.

Edit: pourquoi le code serait travail: bien, tout d'abord, le fait que les instructions peut être exécuté hors d'usage ne garantit pas qu'ils < em> être. En second lieu, il est possible que (au moins certaines implémentations de) sleep contiennent sérialisation instructions qui empêchent rdtsc d'être réarrangé autour d'elle, tandis que d'autres ne le font pas (ou peut les contenir, mais seulement de les exécuter dans des circonstances particulières (mais non précisée)).

Qu'est-ce que vous vous retrouvez avec un comportement qui pourrait changer avec presque tous les recompilation, ou même entre un point et l'autre. Il pourrait produire des résultats extrêmement précis des dizaines de fois dans une rangée, puis échouer pour certains (presque) raison complètement inexplicables (par exemple, quelque chose qui est arrivé dans un autre processus tout à fait).

Autres conseils

Je ne peux pas dire avec certitude ce qui est exactement le problème avec votre code, mais vous faites un peu de travail inutile pour une telle instruction simple. Je vous recommande de simplifier votre code rdtsc considérablement. Vous n'êtes pas obligé de faire des mathématiques 64 bits porte votre auto, et vous n'avez pas besoin de stocker le résultat de cette opération en double. Vous n'avez pas besoin d'utiliser des sorties séparées dans votre asm en ligne, vous pouvez dire à GCC d'utiliser EAX et EDX.

Voici une version très simplifiée de ce code:

#include <stdint.h>

uint64_t rdtsc() {
    uint64_t ret;

# if __WORDSIZE == 64
    asm ("rdtsc; shl $32, %%rdx; or %%rdx, %%rax;"
        : "=A"(ret)
        : /* no input */
        : "%edx"
    );
#else
    asm ("rdtsc" 
        : "=A"(ret)
    );
#endif
    return ret;
}

Aussi, vous devriez envisager d'imprimer les valeurs que vous obtenez de ce que vous puissiez voir si vous sortir 0s, ou autre chose.

Quant à VMWare, jetez un oeil à la spécification de maintien de temps (lien PDF ), ainsi que ce fil . TSC instructions sont (en fonction du système d'exploitation invité):

  • est passé directement au matériel réel (invité PV)
  • Nombre de cycles en la machine virtuelle est exécutée sur le processeur hôte (Windows / etc)

Note, en # 2 en la machine virtuelle est exécutée sur le processeur hôte. Le même phénomène irait pour Xen, ainsi, si je me souviens bien. Essentiellement, vous pouvez vous attendre que le code devrait fonctionner comme prévu sur un invité paravirtualisés. Si émulé, son tout à fait déraisonnable d'attendre du matériel comme la cohérence.

Vous avez oublié d'utiliser volatile dans votre déclaration asm , vous dites au compilateur que l'instruction asm produit le même résultat à chaque fois, comme une pure fonction. (volatile est seulement implicite pour les états de asm sans sorties.)

Ceci explique pourquoi vous obtenez exactement zéro:. Le compilateur optimisé end-start à 0 au moment de la compilation, par le CST (élimination commune-sous-expression)

Voir ma réponse sur Obtenez compte du cycle de CPU pour la __rdtsc() intrinsèque et @ la réponse de Mysticial il a travaille GNU C asm en ligne, que je vais citer ici:

// prefer using the __rdtsc() intrinsic instead of inline asm at all.
uint64_t rdtsc(){
    unsigned int lo,hi;
    __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
    return ((uint64_t)hi << 32) | lo;
}

Cela fonctionne correctement et efficacement 32 et le code 64 bits.

hmmm Je ne suis pas positif, mais je soupçonne que le problème peut être à l'intérieur de cette ligne:

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

Je me méfie si vous pouvez en toute sécurité effectuer ces multiplications énormes dans un « non signé » ... est pas souvent un nombre de 32 bits? ... juste le fait que vous ne pouviez pas en toute sécurité multiplier par 2 ^ 32 et a dû l'ajouter en supplément « * 4 » ajouté au 2 ^ 30 à la fin laisse déjà entrevoir cette possibilité ... vous pourriez avoir besoin de convertir chaque sous-composant et salut lo à un double (au lieu d'un seul à la fin) et à faire la multiplication en utilisant les deux doubles

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top