如何通過程序估計cache大小?
通過訪問數組元素等一些簡單的方式估計cache大小
Talk is cheap,上代碼。基於標準C++實現。
#include &
#include &
#include &
#include &
#define KB(x) ((size_t) (x) &<&< 10)
using namespace std;
int main()
{
//需要測試的數組大小
vector&
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&
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&
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;
}
~&> 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
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 這些術語是什麼關係?