2015-11-02 141 views
0

我編寫了一個簡單的程序來測量使用RDTSC指令的代碼執行時間。但我不知道我的結果是否正確,並且我的代碼有問題......我不知道如何驗證它。使用RDTSC指令在C中測量代碼執行時間

#include <stdio.h> 
#include <assert.h> 
#include <stdint.h> 
#include <stdlib.h> 

#define N (1024*4) 

unsigned cycles_low, cycles_high, cycles_low1, cycles_high1; 

static __inline__ unsigned long long rdtsc(void) 
{ 
    __asm__ __volatile__ ("RDTSC\n\t" 
      "mov %%edx, %0\n\t" 
      "mov %%eax, %1\n\t": "=r" (cycles_high), "=r" (cycles_low):: 
      "%rax", "rbx", "rcx", "rdx"); 
} 

static __inline__ unsigned long long rdtsc1(void) 
{ 
    __asm__ __volatile__ ("RDTSC\n\t" 
      "mov %%edx, %0\n\t" 
      "mov %%eax, %1\n\t": "=r" (cycles_high1), "=r" (cycles_low1):: 
      "%rax", "rbx", "rcx", "rdx"); 
} 

int main(int argc, char* argv[]) 
{ 
    uint64_t start, end; 

    rdtsc(); 
    malloc(N); 
    rdtsc1(); 

    start = (((uint64_t)cycles_high << 32) | cycles_low); 
    end = (((uint64_t)cycles_high1 << 32) | cycles_low1); 

    printf("cycles spent in allocating %d bytes of memory: %llu\n",N, end - start); 

    return 0; 
} 
+1

您需要通過添加cpuid指令或使用rdtscp將序列化添加到rdtsc。請參閱英特爾有關進行測量的最佳做法的白皮書。 http://www.intel.com/content/www/us/en/embedded/training/ia-32-ia-64-benchmark-code-execution-paper.html。 – Imran

+0

請勿使用'RDTSC'。使用「時鐘」或操作系統特定的功能。對於Linux閱讀[時間(7)](http://man7.org/linux/man-pages/man7/time.7.html),然後使用[clock_gettime(2)](http://man7.org/linux /man-pages/man2/clock_gettime.2.html) –

+0

@BasileStarynkevitch感謝您的回覆。我正在研究內核2.4.37,我可否知道你推薦哪個函數來測量內核中的時間?謝謝你的時間。 – HuangJie

回答

2

有使用RDTSC時,你應該記住時間有些東西在(非顯而易見性)問題:

  1. 可統計可能是不可預測的時鐘頻率。在較舊的硬件上,頻率可能實際上在兩條RDTSC指令之間變化,即使在固定的較新硬件上,也很難說出它運行的頻率。

  2. 由於RDTSC沒有輸入,CPU本身可能會將RDTSC指令重新排序,以便在您嘗試測量的代碼之前出現。請注意,這與編譯器對代碼重新排序的問題不同,您使用__volatile__避免了這一問題。爲了有效地避免這種情況,你必須執行一個序列化指令,這是一個指令,它會阻止CPU在它之前移動指令。您可以使用CPUID或RDTSCP(這只是RDTSC的序列化形式)

我的建議是:只要使用任何高頻計時器API您的操作系統了。在Windows上,這是QueryPerformanceCounter,在Unix上你有gettimeofday或clock_gettime。

除此之外,您的RDTSC代碼有幾個結構性問題。返回類型是「無符號long long」,但實際上沒有返回。如果您修復了這個問題,您可以避免將結果存儲在全局變量中,並且可以避免編寫多個版本。

+0

它是[clock_gettime(2)](http://man7.org/linux/man-pages /man2/clock_gettime.2.html)POSIX系統(不是'clock_gettimeofday') –

+0

好,我現在就編輯它。 –

0

注意:在我寫這篇文章時,我想出了一個更簡單/更簡潔的方法來校準TSC轉換因子。所以,繼續閱讀...

如果你願意,在linux下[其他一些操作系統有相似的 - 例如。 BSD實現Linux的一部分的/ proc],在/proc/cpuinfo,你會看到字段是這樣的:

bogomips : 5306.71 
flags  : blah blah2 constant_tsc 
processor : blah 

如果讀此文件,bogomips是以MHz的總CPU頻率[排序]系統期間計算開機。如果您的機器有速度步驟,請優先選擇cpu Mhz

要使用bogomips,請計算processor行的數量,並將bogomips除以它。注意去除「。」並把它當作Khz並使用整數數學。

如果你有constant_tsc,該TSC會一直在這個[最大]頻率運行,並會永遠變化,如果不考慮特定的核心是由於速邁放緩。

如果閱讀/proc/cpuinfo讓你嬌氣,還有另一種方法來校準/確定TSC頻率。

執行以下操作:

tsc1 = rdtsc 
clk1 = clock_gettime 

// delay for a while 
for (i = 1; i < 1000000; ++i) 
    asm volatile ("" ::: "memory"); 

clk2 = clock_gettime 
tsc2 = rdtsc 

有了這些值,可以計算TSC頻率。做上述幾千次。採取最低三角洲 - 這防範了操作系統時間切出你的那些測量。

使用不會導致時間片的循環計數值的最大值。實際上,您可以用替換爲tv_sec = 0, tv_nsec = 500000(500 us)。 nanosleep比equiv usleep好得多。事實上,如果你想要的話,你可以在nanosleep的時間爲2-3秒。

clk2 - clk2值轉換]到秒的小數部分,給你tsc2 - tsc1校準和從TSC蜱和秒轉換到/。

可能影響你得到的結果
1

的問題是:

  • 上最先進的80x86 CPU的TSC措施固定頻率的時鐘,而不是週期,因此,同一段代碼可以有很大的不同「循環」取決於電源管理,同一內核中的其他邏輯CPU的負載(超線程),其他內核的負載(渦輪增壓),CPU溫度(熱節流)等。

  • 什麼都不能阻止操作系統調度程序在第一個rdtsc();之後立即搶佔線程,導致產生的「花費的週期分配「來包含CPU花費在執行任意數量完全不同的進程上的時間。

  • 在某些計算機上,不同CPU上的TSC未同步;沒有什麼能夠阻止操作系統在第一個rdtsc();之後馬上搶佔你的線程,然後在完全不同的CPU上運行你的線程(使用完全不同的TSC)。在這種情況下,end - start可能是負值(如時間正在倒退)。

  • 沒有從第一rdtsc();後立即中斷導致最後的「循環花費分配」包括操作系統花費了處理任何數量的IRQ的時候你的代碼防止一個IRQ(從硬件)。

  • 無法防止導致CPU進入SMM(「系統管理模式」)的SMI(「系統管理中斷」)並且在第一個導致產生的「花費分配的週期」後面執行隱藏的固件代碼以包括CPU花費執行固件代碼的時間。

  • 一些(舊)的CPU有一個錯誤在那裏rdtsc給出了狡猾的結果時低32位溢出(例如,當TSC從0x00000000FFFFFFFF去0x0000000100000000你可以在精確的錯誤的時間使用rdtsc並獲得0x0000000000000000)。

  • 沒有什麼能夠阻止「無序」的現代CPU重新排列大多數指令的執行順序,包括您的rdtsc指令。您的測量包括測量開銷(例如,如果rdtsc需要5個週期,並且您的malloc()需要20個週期,則報告25個週期而不是20個週期)。

  • 有或沒​​有虛擬機;有可能rdtsc指令是虛擬化的(例如,除了常識之外,其他任何東西都不能阻止內核報告有多少可用磁盤空間或其他喜歡的東西)。理想情況下,rdtsc應該虛擬化,以防止上面提到的大多數問題和/或阻止定時副信道(但幾乎從不)。

  • 對於極其舊的CPU(80486及更早版本),TSC和rdtsc指令不存在。


注:我不是在GCC的內嵌彙編的專家;但我強烈懷疑你的宏是越野車和編譯器可以選擇生成這樣的事情:

rdtsc 
    mov %edx, %eax  ;Oops, trashed the low 32 bits 
    mov %eax, %ebx 

應該可以告訴GCC該值/ S在EDX返回:EAX,擺脫的mov指示完全。

0

32位平臺有「= A」。這從eax和edx創建了64位結果。令人遺憾的是,在64位平臺上,它僅僅意味着rax寄存器,這沒有任何幫助。

相反,更好的是,您可以使用「__builtin_ia32_rdtsc()」內部函數直接返回一個64位無符號整數。對於rdtscp也是如此(它也返回當前內核)。請參閱gcc手冊。與使用inline asm手動執行代碼相比,這些代碼發出的代碼稍好,並且可以在32位和64位之間移植。

如果在/ proc/cpuinfo標誌中設置了「constant_tsc」,則無論CPU頻率如何縮放,TSC都以恆定速率運行。如果設置了「nonstop_tsc」,則TSC繼續以C(睡眠)狀態運行。如果兩者都設置好了,計數器「應該」也是跨核心同步的(至少在最近的CPU,Core i7或更高版本上)。我對最後一點不太確定,也許有人能糾正我?