2010-05-11 4 views
4

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

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

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

+2

Хотя вопрос был (немного) другим, большая часть моего ответа на: http : //stackoverflow.com/questions/2658699/measure-time-to-execute-single-instruction/2658833#2658833 применяется здесь. –

+0

@ Jerry Coffin Я не вижу, как ваш ответ на этот вопрос помогает мне. Но чем больше, я не понимал большую часть того, что вы написали lol. –

+0

@Tim Post: Его проблема не в том, что (по крайней мере, исключительно) VMWare - это то, что RDTSC может быть выполнен из строя, поэтому без выполнения команды сериализации (обычно это «CPUID») она дает почти бессмысленные результаты. –

ответ

1

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

rdtsc 
push 1 
call sleep 
rdtsc 

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

rdtsc 
rdtsc 
push 1 
call sleep 

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

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

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

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

+0

Я только что ворвался в VMWare, сохраняя спецификацию, его код _should_ работал. Из docs, rdtsc _should_ работает так, как ожидалось, хотя я уверен, что точность лучше при паравиртуализации. –

+4

Выполнение заказа не будет зависеть от его проблемы. Сон не является одной инструкцией, и никакая сумма не в порядке исполнения будет переупорядочить оба RDTSC, чтобы быть рядом друг с другом. Даже если это так, на процессоре Intel rdtsc увеличивается на каждый тактовый цикл чипа. Невозможно, чтобы два вызова, даже если последовательное, возвращали одно и то же значение. – SoapBox

+0

Я согласен с SoapBox - ведь внутри функции 'sleep()' есть вызов в ядро; за этим «вызовом» скрывается очень много инструкций. – caf

1

Что касается VMWare, посмотрите на the time keeping spec (PDF Link), а также this thread. инструкции TSC являются (в зависимости от гостевой ОС):

  • Зачет непосредственно к реальным устройствам (PV гость)
  • циклов граф в то время как ВМ выполняется на хост-процессор (Windows/и т.д.)

Обратите внимание, что в №2 в то время как VM выполняется на основном процессоре. То же самое было бы для Xen, если я правильно вспомню. В сущности, вы можете ожидать, что код будет работать, как ожидалось, у паравиртуализированного гостя. Если эмулировать, его совершенно необоснованно ожидать от аппаратного обеспечения, такого как согласованность.

2

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

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

+0

Я получаю компиляцию кода ошибки: 'ожидаемый строковый литерал до ')' токен'. –

+0

Исправлено, извините. – SoapBox

+0

Теперь он компилируется, спасибо за пример. Тем не менее, я до сих пор получаю ярость от нуля. Я не думаю, что проблема не в том, как выполняется rdtsc. И поскольку это не устраняет мою проблему, я скорее использую код, предоставленный инструктором. –

0

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

результат = (двойная) привет * (1 < < 30) * 4 + се;

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

Смежные вопросы