Расчет частоты процессора в C с помощью RDTSC всегда возвращает 0

StackOverflow https://stackoverflow.com/questions/2814569

Вопрос

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

#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.Это исключает проблему на виртуальной машине, о которой я сначала подумал.

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

Это было полезно?

Решение

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

rdtsc
push 1
call sleep
rdtsc

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

rdtsc
rdtsc
push 1
call sleep

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

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

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

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

Другие советы

Я не могу сказать наверняка, что именно не так с вашим кодом, но для такой простой инструкции вы делаете довольно много лишней работы.Я рекомендую вам упростить rdtsc код существенно.Вам не нужно выполнять 64-битные математические вычисления самостоятельно, и вам не нужно сохранять результат этой операции как двойной.Вам не нужно использовать отдельные выходные данные во встроенном asm, вы можете указать 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 или что-то еще.

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

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

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

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

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

Смотрите мой ответ на Получить количество циклов процессора? для __rdtsc() встроенный, и в ответе @Mysticial есть работающий встроенный ассемблер 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 << 30) * 4 + lo;

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top