百度的一道面試題(關於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 XX    15   
#define YY    1023
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的命中率。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章