1. 程式人生 > >百度的一道面試題(關於Cache的)

百度的一道面試題(關於Cache的)

某型CPU的一級資料快取大小為16K位元組,cache塊大小為64位元組;二級快取大小為256K位元組,cache塊大小為4K位元組,採用二路組相聯。經測試,下面兩段程式碼執行時效率差別很大,請分析哪段程式碼更好,以及可能的原因。

為了進一步提高效率,你還可以採取什麼辦法?

A段程式碼:

int matrix[1023][15];

const char *str = "this is a str";

int i, j, tmp, sum = 0;

tmp = strlen(str);

for(i = 0; i < 1023; i++)

for(j = 0; j < 15; j++)

sum += matrix[i][j] + tmp;

B段程式碼 :

int matrix[1025][17];

const char *str = "this is a str";

int i, j, sum = 0;

for(i = 0; i < 17; i++)

for(j = 0; j < 1025; j++)

sum += matrix[j][i] + strlen(str);

A段程式碼效率要遠遠高於B段程式碼,原因有三:

1、

B效率低最要命的地方就是每次都要呼叫strlen()函式,這是個嚴重問題,屬於邏輯級錯誤。假設A的兩層迴圈都不改變,僅僅是把A的那個迴圈裡面的temp換成strlen()呼叫,在Windows 2000 (Intel 雙) 下測試,竟然是A的執行時間的3.699倍。(這裡沒有涉及不同CPU有不同的Cache設計)僅僅是這一點就已經說明B段程式碼垃圾程式碼。

2

這也是一個邏輯級的錯誤。在這裡我們再做個試驗,AB段程式碼分別採用大小一樣的陣列[1023][15][1023][16][1023][17],只是在迴圈上採取了不同的方式。兩者在執行時間上也是有很大差異的了。B的執行時間大概是A1.130倍。

那麼這是因為什麼呢?其實也很簡單,那就是A段程式碼中的迴圈執行語句對記憶體的訪問是連續的,而B段程式碼中的迴圈執行語句對記憶體的訪問是跳躍的。直接降低了B程式碼的執行效率。

這裡不是內層迴圈執行多少次的問題,而是一個對記憶體訪問是否連續的問題。

3、

A的二維陣列是[1023][15],B的二維陣列是[1027][17],在這裡B段程式碼有犯了一個CPU級錯誤(或者是Cache級的錯誤)。

因為在Cache中資料或指令是以行為單位儲存的(也可以說是Cache),一行又包含了很多字。如現在主流的設計是一行包含64Byte。每一行擁有一個Tag。因此,假設CPU需要一個標為Tag 1的行中的資料,它會通過CAMCache中的行進行查詢,一旦找到相同Tag的行,就對其中的資料進行讀取。

A的是15 *4B 60B,一個Cache行剛好可以儲存。B的是17*4B 68B,超過了一個Cache行所儲存的資料。很明顯17的時候命中率要低於15的時候。

現在我們先不管AB的迴圈巢狀的順序,僅僅拿A段程式碼來做個試驗,我們將會分三種情況來進行:

[1023][15][1023][16][1023][17]

執行結果並沒有出乎意料之外 17 的時候的執行時間大概是 15 的時候的1.399倍,除去有因為17的時候多執行迴圈,17/15 1.133 。進行折算,17的時候大概是15的時候的1.265倍。

16的時候的執行時間要比15的時候的執行時間要短,因為是16的時候,Cache命中率更高。16/15 1.066 ,而15的執行時間卻是161.068倍,加上16多執行的消耗,進行折算,15的時候大概是16的時候執行時間的1.134倍。

因為A段程式碼是15,而B段程式碼是17,在這一點上B段程式碼的效率要低於A段程式碼的效率。這是一個CPU級的錯誤(或者是Cache級的錯誤),這裡涉及到Cache的塊大小,也就涉及到Cache命中率,也就影響到程式碼效率。

不再假設什麼,僅僅對A段和B段程式碼進行測試,B段程式碼的執行效率將是A段程式碼執行效率的3.95倍。當然最大的罪魁禍首就是B中的重複呼叫strlen()函式。後面兩個錯誤告訴我們當需要對大量資料訪問的時候,一定要注意對記憶體的訪問要儘量是連續而且迴圈內層的訪問接近Cache的塊大小,以提高Cache的命中率,從而提高程式的執行效率。

所以可以對程式碼進行一下修改:

#define XX15

#define YY1023

int matrix[XX][YY];

const char *str = "this is a str";

int i, j, tmp, sum = 0;

tmp = strlen(str);

for(i = 0; i < XX; i++)

for(j = 0; j < YY; j++)

sum += matrix[i][j] + tmp;

這個程式僅僅是把陣列的宣告給顛倒了一下,迴圈也顛倒了一下,看起來和執行起來和上面給出的A段程式碼沒有多大的區別。但是如果當XX很小,比如:8,那麼這段程式和給出的A段程式碼就有區別了。這是因為這樣做可以提高Cache的命中率。