Расчет частоты процессора в C с помощью RDTSC всегда возвращает 0 - PullRequest
4 голосов
/ 12 мая 2010

Следующий фрагмент кода был передан нам от нашего инструктора, чтобы мы могли измерить производительность некоторых алгоритмов:

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

Однако мне нужен этот код для переноса на машины с разными частотами процессора. Для этого я пытаюсь вычислить частоту процессора компьютера, на котором выполняется код, следующим образом:

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

Проблема в том, что результат всегда равен 0, и я не могу понять, почему. Я использую Linux (Arch) в качестве гостя на VMware.

На компьютере друга (MacBook) он работает в некоторой степени; Я имею в виду, что результат больше 0, но он переменный, потому что частота процессора не фиксирована (мы пытались это исправить, но по какой-то причине мы не можем это сделать). У него есть другая машина, на которой установлен Linux (Ubuntu) в качестве хоста, и он также сообщает 0. Это исключает проблему, связанную с виртуальной машиной, которая, как мне показалось, сначала была.

Есть идеи, почему это происходит и как я могу это исправить?

Ответы [ 5 ]

2 голосов
/ 12 мая 2010

Я не могу точно сказать, что именно не так с вашим кодом, но вы выполняете довольно много ненужной работы для такой простой инструкции. Я рекомендую вам существенно упростить код rdtsc. Вам не нужно выполнять 64-битную математику самостоятельно, и вам не нужно хранить результат этой операции как двойной. Вам не нужно использовать отдельные выходы в вашем встроенном ассемблере, вы можете указать GCC использовать eax и edx.

Вот очень упрощенная версия этого кода:

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

Также вам следует рассмотреть возможность распечатки значений, которые вы получаете из этого, чтобы вы могли видеть, получаете ли вы 0 или что-то еще.

2 голосов
/ 12 мая 2010

Хорошо, так как другой ответ не помог, я постараюсь объяснить более подробно. Проблема в том, что современный процессор может выполнять инструкции не по порядку. Ваш код начинается примерно так:

rdtsc
push 1
call sleep
rdtsc

Современные процессоры не обязательно выполняют инструкции в их первоначальном порядке. Несмотря на ваш первоначальный порядок, процессор (в основном) может выполнять, как:

rdtsc
rdtsc
push 1
call sleep

В этом случае ясно, почему разница между двумя rdtsc с будет (по крайней мере, очень близка к) 0. Чтобы предотвратить это, вам нужно выполнить инструкцию, что ЦП будет никогда переставить выполнять из строя. Самая распространенная инструкция для этого - CPUID. Другой ответ, который я связал, должен (если память не изменяет) начаться примерно оттуда, о шагах, необходимых для правильного / эффективного использования CPUID для этой задачи.

Конечно, возможно, что Тим Пост был прав, и вы также видите проблемы из-за виртуальной машины. Тем не менее, в его нынешнем виде нет гарантии, что ваш код будет работать правильно даже на реальном оборудовании.

Изменить: почему код будет работать: во-первых, тот факт, что инструкции могут выполняться не по порядку, не гарантирует, что они будет будет. Во-вторых, возможно, что (по крайней мере, некоторые реализации) sleep содержат инструкции сериализации, которые не позволяют переставить rdtsc вокруг него, в то время как другие этого не делают (или могут содержать, но выполняют только при определенных (но не указанных) обстоятельства).

То, что у вас осталось, - это поведение, которое может измениться практически при любой повторной компиляции или даже между одним запуском и следующим. Он может давать чрезвычайно точные результаты десятки раз подряд, а затем потерпеть неудачу по какой-то (почти) совершенно необъяснимой причине (например, что-то, что произошло в каком-то другом процессе полностью).

1 голос
/ 18 августа 2018

Вы забыли использовать volatile в своем операторе asm , поэтому вы говорите компилятору, что оператор asm каждый раз выдает один и тот же вывод, как чистая функция. (volatile подразумевается только для asm операторов без выходных данных.)

Это объясняет, почему вы получаете точно ноль: компилятор оптимизировал end-start до 0 во время компиляции с помощью CSE (исключение общих подвыражений).

См. Мой ответ по Получить счетчик циклов ЦП? для встроенного __rdtsc(), а в ответе @ Mysticial есть рабочий встроенный asm GNU C, который я приведу здесь:

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

Это работает правильно и эффективно для 32- и 64-битного кода.

1 голос
/ 12 мая 2010

Что касается VMWare, взгляните на спецификацию времени (PDF Link), а также эту ветку . Инструкции TSC (в зависимости от гостевой ОС):

  • Передано непосредственно к реальному оборудованию (гостевой PV)
  • Подсчет циклов , пока виртуальная машина исполняется на хост-процессоре (Windows / etc)

Обратите внимание, что в # 2 , в то время как виртуальная машина исполняется на хост-процессоре. То же самое произойдет и с Ксеном, если я правильно помню. По сути, вы можете ожидать, что код должен работать как ожидается на паравиртуализированном госте. В случае эмуляции совершенно необоснованно ожидать аппаратного обеспечения, такого как согласованность.

0 голосов
/ 09 ноября 2011

хммм Я не уверен, но я подозреваю, что проблема может быть в этой строке:

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

Я подозреваю, что вы можете безопасно выполнять такие огромные умножения в "без знака" ... разве это не 32-разрядное число? ... просто тот факт, что вы не могли безопасно умножить на 2 ^ 32 и пришлось добавить его как дополнительный "* 4", добавленный к 2 ^ 30 в конце, уже намекает на такую ​​возможность ... вам может понадобиться преобразовать каждый подкомпонент hi и lo в двойное число (вместо одного в самом конце) и выполнить умножение с использованием двух двойных чисел

...