Pergunta

A seguinte parte do código nos foi dada de nosso instrutor para que pudéssemos medir alguns algoritmos de desempenho:

#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;
}

No entanto, preciso que esse código seja portátil para máquinas com diferentes frequências da CPU. Para isso, estou tentando calcular a frequência da CPU da máquina onde o código está sendo executado assim:

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;
}

O problema é que o resultado é sempre 0 e não consigo entender o porquê. Estou executando o Linux (Arch) como convidado no VMware.

Na máquina de um amigo (MacBook), está trabalhando até certo ponto; Quero dizer, o resultado é maior que 0, mas é variável porque a frequência da CPU não é fixa (tentamos corrigi -lo, mas por algum motivo não somos capazes de fazê -lo). Ele tem uma máquina diferente que está executando o Linux (Ubuntu) como host e também relata 0. Isso descarta o problema na máquina virtual, que eu pensei que era o problema no início.

Alguma idéia de por que isso está acontecendo e como posso consertar isso?

Foi útil?

Solução

Ok, como a outra resposta não foi útil, tentarei explicar mais detalhes. O problema é que uma CPU moderna pode executar instruções fora de ordem. Seu código começa como algo como:

rdtsc
push 1
call sleep
rdtsc

CPUs modernas fazem não necessariamente executar instruções em sua ordem original. Apesar do seu pedido original, a CPU é (principalmente) livre para executar isso como:

rdtsc
rdtsc
push 1
call sleep

Nesse caso, fica claro por que a diferença entre os dois rdtscS estaria (pelo menos muito próximo de) 0. Para evitar isso, você precisa executar uma instrução de que a CPU irá Nunca reorganize para executar fora de ordem. A instrução mais comum a ser usada para isso é CPUID. A outra resposta que vinculei deve (se a memória servir) começar aproximadamente a partir daí, sobre as etapas necessárias para usar CPUID corretamente/efetivamente para esta tarefa.

Claro, é possível que Tim Post estivesse certo e você está também vendo problemas por causa de uma máquina virtual. No entanto, como está agora, não há garantia de que seu código funcione corretamente, mesmo em hardware real.

Editar: sobre por que o código gostaria Trabalho: Bem, antes de tudo, o fato de que as instruções posso ser executado fora de ordem não garante que eles vai ser. Segundo, é possível que (pelo menos algumas implementações de) sleep contêm instruções serializadas que evitam rdtsc de serem reorganizados em torno dele, enquanto outros não (ou podem conter -os, mas apenas os executam em circunstâncias específicas (mas não especificadas)).

O que você resta é o comportamento que pode mudar com quase qualquer recompilação, ou mesmo apenas entre uma corrida e a próxima. Pode produzir resultados extremamente precisos dezenas de vezes seguidas e depois falhar por algum (quase) motivo completamente inexplicável (por exemplo, algo que aconteceu em algum outro processo completamente).

Outras dicas

Não posso dizer ao certo o que exatamente há de errado com seu código, mas você está fazendo um trabalho desnecessário para uma instrução tão simples. Eu recomendo que você simplifique o seu rdtsc código substancialmente. Você não precisa fazer matemática de 64 bits, e não precisa armazenar o resultado dessa operação como dupla. Você não precisa usar saídas separadas no seu ASM embutido, pode dizer ao GCC para usar o EAX e o EDX.

Aqui está uma versão muito simplificada deste código:

#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;
}

Além disso, considere imprimir os valores que está saindo disso para poder ver se está saindo de 0s ou outra coisa.

Quanto ao VMware, dê uma olhada o tempo mantendo as especificações (Link pdf), bem como este tópico. As instruções do TSC são (dependendo do sistema operacional convidado):

  • Passado diretamente para o hardware real (convidado PV)
  • Ciclos de contagem enquanto A VM está executando no processador host (Windows / etc)

Nota, no #2 o enquanto A VM está executando no processador host. O mesmo fenômeno também iria para Xen, se bem me lembro. Em essência, você pode esperar que o código funcione conforme o esperado em um hóspede paravartualizado. Se imitada, é totalmente irracional esperar hardware como consistência.

Você esqueceu de usar volatile Na sua declaração ASM, então você está dizendo ao compilador que o asm A declaração produz a mesma saída sempre, como uma função pura. (volatile só está implícito para asm declarações sem saídas.)

Isso explica por que você está recebendo exatamente Zero: o compilador otimizado end-start para 0 No momento da compilação, através da CSE (eliminação de comuns-subexpressão).

Veja minha resposta em Obter contagem do ciclo da CPU? para o __rdtsc() A resposta intrínseca, e @ @mysticial, tem funcionando GNU c inline ASM, que vou citar aqui:

// 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;
}

Isso funciona de maneira correta e eficiente para o código de 32 e 64 bits.

Hmmm, não tenho certeza, mas suspeito que o problema possa estar dentro desta linha:

resultado = (duplo) oi * (1 << 30) * 4 + lo;

Suspeito se você pode realizar com segurança uma multiplicações tão grandes em um "não assinado" ... Isso geralmente não é um número de 32 bits? ... apenas o fato de você não poder se multiplicar com segurança por 2^32 e ter que anexá -lo como um "* 4" adicionado ao 2^30 no final já sugere essa possibilidade ... você pode precisar Converta cada subcomponente Hi e Lo em um duplo (em vez de um único no final) e faça a multiplicação usando os dois duplos

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top