簡體   English   中英

使用RDTSC以C計算CPU頻率始終返回0

[英]Calculating CPU frequency in C with RDTSC always returns 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;
}

但是,我需要將此代碼移植到具有不同CPU頻率的機器上。 為此,我正在嘗試計算運行代碼的機器的CPU頻率,如下所示:

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,我不明白為什么。 我在VMware上作為訪客運行Linux(Arch)。

在朋友的機器上(MacBook)它在某種程度上起作用; 我的意思是,結果大於0但它是可變的,因為CPU頻率沒有固定(我們試圖修復它但由於某種原因我們無法做到)。 他有一台運行Linux(Ubuntu)作為主機的不同機器,它也報告了0.這排除了虛擬機上的問題,我認為這是最初的問題。

任何想法為什么會發生這種情況,我該如何解決?

好的,既然另一個答案沒有幫助,我會嘗試解釋更多細節。 問題是現代CPU可以不按順序執行指令。 你的代碼開頭是這樣的:

rdtsc
push 1
call sleep
rdtsc

現代的CPU 不一定在原來的順序執行指令,但。 盡管你的原始訂單,CPU(大多數)可以自由執行,就像:

rdtsc
rdtsc
push 1
call sleep

在這種情況下,很明顯為什么兩個rdtsc之間的區別(至少非常接近)為了0.為了防止這種情況,你需要執行CPU 永遠不會重新排列以執行亂序的指令。 最常用的指令是CPUID 我鏈接的另一個答案(如果內存服務)大致從那里開始,關於正確/有效地使用CPUID執行此任務所需的步驟。

當然,Tim Post可能是正確的,你也會因為虛擬機而遇到問題。 盡管如此,就目前而言,即使在真實硬件上,也無法保證您的代碼能夠正常工作。

編輯:關於為什么代碼可以工作:首先,指令可以無序執行的事實並不能保證它們被執行。 其次, sleep中的(至少一些實現)可能包含阻止rdtsc在其周圍重新排列的序列化指令,而其他指令則不包含(或者可能包含它們,但僅在特定(但未指定)的情況下執行它們)。

你剩下的是幾乎任何重新編譯都會改變的行為,甚至只是在一次運行和下一次運行之間。 它可以連續幾十次產生極其准確的結果,然后因某些(幾乎)完全無法解釋的原因而失敗(例如,某些其他過程完全發生的事情)。

我不能肯定地說你的代碼究竟出了什么問題,但是你為這樣一個簡單的指令做了很多不必要的工作。 我建議你大大簡化你的rdtsc代碼。 您不需要進行64位數學運算,並且您不需要將該操作的結果存儲為double。 您不需要在內聯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指令(取決於客戶操作系統):

  • 直接傳遞給真正的硬件(光伏客戶)
  • VM的主機處理器上執行(在Windows /等)計數周期

注意,在#2中的虛擬機的主機處理器上執行的一段時間 如果我沒記錯的話,Xen也會出現同樣的現象。 從本質上講,您可以預期代碼應該在半虛擬客戶端上按預期工作。 如果模仿,那么期望硬件就像一致性完全是不合理的。

你忘了在你的asm語句中使用volatile ,所以你告訴編譯器每次asm語句產生相同的輸出,就像純函數一樣。 volatile僅對沒有輸出的asm語句隱含。)

這就解釋了為什么你得到的正是零:編譯器通過CSE(公共子表達式消除)將編譯時的end-start優化為0

請參閱我關於獲取CPU周期數的答案 對於__rdtsc()內在函數和@Mysticial的答案,有一個有效的GNU C inline asm,我在這里引用:

 // 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位代碼正常有效。

嗯,我不是積極的,但我懷疑問題可能出在這一行:

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

我懷疑你是否可以安全地在“無符號”中進行如此大的乘法...這通常不是32位數嗎? ...只是事實上你不能安全地乘以2 ^ 32並且不得不追加它作為額外的“* 4”添加到最后的2 ^ 30已經暗示了這種可能性......你可能需要將每個子組件hi和lo轉換為double(而不是最后一個)並使用兩個雙精度進行乘法運算

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM