2015-05-12 31 views
1

我的目標是使用簡單代碼來測量(不同)緩存的效果。我正在關注這篇文章,特別是第20頁和第21頁: https://people.freebsd.org/~lstewart/articles/cpumemory.pdf測量C++代碼的CPU週期

我正在使用64位的linux。 L1d緩存爲32K,L2爲256K,L3爲25M。

這是我的代碼(I編譯此代碼與克++無標誌):

#include <iostream> 

// *********************************** 
// This is for measuring CPU clocks 
#if defined(__i386__) 
static __inline__ unsigned long long rdtsc(void) 
{ 
    unsigned long long int x; 
    __asm__ volatile (".byte 0x0f, 0x31" : "=A" (x)); 
    return x; 
} 
#elif defined(__x86_64__) 
static __inline__ unsigned long long rdtsc(void) 
{ 
    unsigned hi, lo; 
    __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi)); 
    return ((unsigned long long)lo)|(((unsigned long long)hi)<<32); 
} 
#endif 
// *********************************** 


static const int ARRAY_SIZE = 100; 

struct MyStruct { 
    struct MyStruct *n; 
}; 

int main() { 
    MyStruct myS[ARRAY_SIZE]; 
    unsigned long long cpu_checkpoint_start, cpu_checkpoint_finish; 

    // Initializing the array of structs, each element pointing to the next 
    for (int i=0; i < ARRAY_SIZE - 1; i++){ 
     myS[i].n = &myS[i + 1]; 
     for (int j = 0; j < NPAD; j++) 
      myS[i].pad[j] = (long int) i; 
    } 
    myS[ARRAY_SIZE - 1].n = NULL; // the last one 
    for (int j = 0; j < NPAD; j++) 
     myS[ARRAY_SIZE - 1].pad[j] = (long int) (ARRAY_SIZE - 1); 

    // Filling the cache 
    MyStruct *current = &myS[0]; 
    while ((current = current->n) != NULL) 
     ; 

    // Sequential access 
    current = &myS[0]; 

    // For CPU usage in terms of clocks (ticks) 
    cpu_start = rdtsc(); 

    while ((current = current->n) != NULL) 
     ; 

    cpu_finish = rdtsc(); 

    unsigned long long avg_cpu_clocks = (cpu_finish - cpu_start)/ARRAY_SIZE; 

    std::cout << "Avg CPU Clocks: " << avg_cpu_clocks << std::endl; 
    return 0; 
} 

我有兩個問題:

1-我變化ARRAY_SIZE爲1〜1,000,000(如此的尺寸我的數組範圍在2B到2MB之間),但平均CPU時鐘始終爲​​10.

根據該PDF(第21頁的圖3-10),如果陣列可以達到3-5個時鐘完全適合L1,當超過L1的尺寸時可以獲得更高的數字(9個週期)。

2-如果我將ARRAY_SIZE增加到1,000,000以上,我會得到分段錯誤(core dumped),這是由於堆棧溢出造成的。我的問題是,使用動態分配(MyStruct *myS = new MyStruct[ARRAY_SIZE])是否會導致任何性能損失。

+3

您需要讓您的編譯器進行基準測試優化。所以用'g ++ -O2 -Wall -mtune = native'編譯;不要使用'rdtsc',但讀[時間(7)](http://man7.org/linux/man-pages/man7/time.7.html) –

+0

@BasileStarynkevitch我用這些標誌,現在平均CPU時鐘是4,仍然不管陣列的長度。這就是說,要麼所有的東西都可以適合L1d(事實並非如此),或者預取能夠做出出色的工作(我懷疑)。 – narengi

+0

@BasileStarynkevitch另外,請你告訴我爲什麼rdtsc不適合這個目的? – narengi

回答

3

即使您用於測量執行的方法不可靠,您得到的結果(4個週期)確實有意義,這是原因。被測量的循環包含三條指令(mov,test,branch)。由於分支預測,我們可以假裝我們只有一系列的mov /測試指令。由於超標量執行,mov指令的執行可以與執行前一次迭代的測試指令相重疊。因此,mov決定執行時間。在最新的英特爾處理器中從L1緩存獲取數據的延遲是4個週期。所以假設它存在於L1緩存中,每個元素需要大約4個週期。然而,這只是猜測,但這取決於微體系結構,陣列中元素數量似乎不會影響時間的原因是L1和L2硬件預取器隱藏了從較低級別的存儲器獲取數據的大部分成本系統有效地使L1緩存線的大小無限大。

如果您想評估高速緩存的好處而不是硬件預取,則必須禁用預編制。做到這一點的方式取決於處理器,可能無法實現。

Intel recommends using the rdtscp instruction to measure execution time.正確使用時,它可以提供比clock_gettime或其他任何函數更準確的測量。

關於你的第二個問題。你應該從堆中分配數組。對於這個特定的程序,它不會招致額外的性能損失。

+0

感謝您的回覆。你提到我的測量方法不可靠。我在我的代碼中將rdtsc更改爲rdtscp,並且仍然得到相同的結果。基準測試的適當方法是什麼?我剛剛遇到了英特爾的PCM工具,但遇到了一些問題讓它工作。我肯定會對一種能夠緩存命中/錯誤cnts,每條指令時鐘的工具感興趣。你有任何工具/圖書館嗎? – narengi

+0

這比將rdtsc更改爲rdtscp複雜得多,有幾件事情需要注意確保可靠的測量,如英特爾文章中所述。 –

+0

有很多這樣的工具,如perf和英特爾VTune放大器。 –