[英]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或其他東西。
你忘了在你的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.