如何通過程序估計cache大小?

通過訪問數組元素等一些簡單的方式估計cache大小


Talk is cheap,上代碼。基於標準C++實現。

#include &
#include &
#include &
#include &

#define KB(x) ((size_t) (x) &<&< 10) using namespace std; int main() { //需要測試的數組大小 vector& sizes_KB;
for (int i = 1; i &< 18; i++){sizes_KB.push_back(1 &<&< i);} random_device rd;//隨機數生成 mt19937 gen(rd()); for (size_t size: sizes_KB) { uniform_int_distribution&<&> dis(0, KB(size)-1);
vector& memory(KB(size));//創建一個指定大小的連續內存塊
fill(memory.begin(), memory.end(), 1);

int dummy = 0;//用這個變數避免編譯器對下面的循環進行優化

//在內存塊上進行大量的隨機訪問,並計時
clock_t begin = clock();
for (int i = 0; i &< (1&<&<25); i++){dummy += memory[dis(gen)];} clock_t end = clock(); double elapsed_secs = double(end - begin) / CLOCKS_PER_SEC; cout &<&< size &<&< " KB, " &<&< elapsed_secs &<&< "secs, dummy:" &<&< dummy &<&< endl; } cin.get(); }

思路很簡單:

創建一個連續內存塊,進行連貫、大量、隨機有意義內存訪問。這幾點缺一不可,否則不能保證整塊內存被儘可能的放入Cache。在這種情況下,當內存塊能夠被整塊放入Cache時,平均訪問速度會顯著的快。觀察隨著內存大小提高,平均訪問時間的躍升點,即可估計Cache大小。

代碼說明:vector內部是連續內存塊。通過累加並輸出一個dummy變數,使得內存訪問變得有意義,否則很可能被優化掉。

輸出結果如下:

將結果繪圖:

可見4096K以下時,訪問時間基本固定,但是4096K之後訪問時間有一個躍升。可見某一級的Cache大小約為4096K (4M).

測試用的CPU是i7-6500U,信息如下:

與測試結果接近。

---------------------------------------------------------------------------------------

這個方法正如上面 @陳昱所說,CSAPP封面那個圖就是這樣搞的。

我有一本CSAPP的作者簽名版,液~!


見CSAPP,儲存器那章,介紹了儲存器山,通過對一個足夠大的內存數據,每次讀取不同位置不同size的內存數據。重複多次統計時間消耗的陡坡,來得出各級cache的大小


回答一下在做機器學習高性能優化的時候的一般解決方案:對於最近五六年的Intel CPU來說,你可以通過enumerate CPUID leaf 4來得到不同cache的信息。具體可以參見Intel? 64 Architecture Processor Topology Enumeration。

AMD的方法稍微不一樣一些,但是還是可以做到的。

如果你是通過序貫讀取數組的方法來估計cache的話,現代處理器有大量工作是預測程序的行為,所以執行的時候在底層很容易出現跨cache的優化。另外,實際生產環境中,伺服器經常處在一個多線程高並發的條件下,試圖通過其他access pattern來估計cache的大小,只能說也很有挑戰性。

update:看到 @迪迦奧特曼的回答,大讚。


更靠譜的方式是:

可以參考openblas:

https://github.com/xianyi/OpenBLAS/blob/develop/cpuid_x86.c#L291

或者我們直接點:

#include &
#include &
#include &
#include &
#include &

std::string exec(const char* cmd) {
char buffer[128];
std::string result = "";
std::shared_ptr& pipe(popen(cmd, "r"), pclose);
if (!pipe) throw std::runtime_error("popen() failed!");
while (!feof(pipe.get())) {
if (fgets(buffer, 128, pipe.get()) != NULL)
result += buffer;
}
return result;
}

int main() {
std::string cacheInfo;
#if defined(__OSX__) || defined(__APPLE__)
cacheInfo = exec("sysctl -a | grep -i hw.cache");
#else
cacheInfo = exec("getconf -a | grep -i cache");
#endif
std::cout &<&< cacheInfo; // parse cache Info // ... return 0; }

1. Mac OS X

~&> sysctl -a | grep -i hw.cache

hw.cachelinesize: 64
hw.cachesize: 1717986918432768 262144 6291456 0 0 0 0 0 0
hw.cacheconfig: 8 2 2 8 0 0 0 0 0 0

2. Linux

~&> getconf -a | grep -i cache

LEVEL1_ICACHE_SIZE 32768
LEVEL1_ICACHE_ASSOC 8
LEVEL1_ICACHE_LINESIZE 64
LEVEL1_DCACHE_SIZE 32768
LEVEL1_DCACHE_ASSOC 8
LEVEL1_DCACHE_LINESIZE 64
LEVEL2_CACHE_SIZE 262144
LEVEL2_CACHE_ASSOC 8
LEVEL2_CACHE_LINESIZE 64
LEVEL3_CACHE_SIZE 2097152
LEVEL3_CACHE_ASSOC 8
LEVEL3_CACHE_LINESIZE 64
LEVEL4_CACHE_SIZE 0
LEVEL4_CACHE_ASSOC 0
LEVEL4_CACHE_LINESIZE 0


直接問操作系統


這個要說清楚得碼一大堆字。為了減少篇幅,只談 @邵天蘭 的答案忽略的2點。

1. 「平均訪問時間的躍升點」不一定存在。對於全相聯(full associative)結構的cache,當訪問範圍開始稍大於cache尺寸後所有的cache slot都會在每一輪重複訪問中發生replacement,導致完全的miss而出現訪問時間躍升;但是對於直接映射(direct map)結構的cache,101%cache大的訪問範圍只能導致1%的cache slot出現replacement,其他99%的slot不會被replace的(因為範圍內只有一個地址能映射過去)。訪問時間是從1倍cache尺寸後逐步變大,直到訪問範圍達到2倍cache尺寸後訪問時間飽和,沒有躍升點;而n way set associative結構的cache介乎2者之間,依賴於n多大。這也是判斷cache結構的一個方法。

2. 現在CPU的Replacement演算法越來越智能,在超線程、多核之類的CPU上更是會動態控制某一線程、核能用到的cache尺寸。

此外:一般程序看到的連續虛擬地址物理上並不一定連續。


有人解決了就好。

我只知道cache cache…


推薦閱讀:

用天河二號打 CF 是一種怎樣的體驗?
內存的頻率和容量哪個性能影響更大,更重要?
為什麼內存的大小這麼多年沒有質的提高?
虛擬內存設到哪個分區比較好啊?4g的內存,虛擬內存設置多大比較合適啊?
UEFI /windows boot manager /MBR /syslinux /NT loader /GRUB /變色龍 /Clover 這些術語是什麼關係?

TAG:程序員 | 編程 | 計算機 | 內存RAM |