[英]Linux C++: how to profile time wasted due to cache misses?
我知道我可以使用 gprof 來對我的代碼進行基准測試。
但是,我有這個問題——我有一個智能指針,它具有額外的間接級別(將其視為代理對象)。
結果,我有這個額外的層,它影響幾乎所有的功能,並帶有緩存。
有沒有辦法測量我的 CPU 由於緩存未命中而浪費的時間?
您可以嘗試使用cachegrind ,它是前端 kcachegrind。
Linux 從 2.6.31 開始支持perf
。 這允許您執行以下操作:
perf record -e LLC-loads,LLC-load-misses yourExecutable
perf report
LLC-load-misses
行,annotate
。 您應該看到行(在匯編代碼中,被原始源代碼包圍)和一個數字,指示發生緩存未命中的行的最后一級緩存未命中的比例。您可以找到一個訪問 CPU 性能計數器的工具。 每個內核中可能都有一個寄存器,用於計算 L1、L2 等未命中數。 或者 Cachegrind 執行逐周期模擬。
但是,我認為這不是很有見地。 您的代理對象大概是由它們自己的方法修改的。 傳統的分析器會告訴您這些方法花費了多少時間。 沒有配置文件工具會告訴您如果沒有緩存污染源,性能將如何提高。 這是減少程序工作集的大小和結構的問題,這不容易推斷。
一個快速的谷歌搜索出現了boost::intrusive_ptr
你可能會感興趣。 它似乎不支持諸如weak_ptr
東西,但是轉換您的程序可能很簡單,然后您肯定會知道非侵入性引用計數的成本。
繼續遵循@Mike_Dunlavey 的回答:
首先,使用您喜歡的工具獲取基於時間的配置文件:VTune 或 PTU 或 OProf。
然后,獲取緩存未命中配置文件。 L1 緩存未命中,或 L2 緩存未命中,或...
即,第一個配置文件將“花費的時間”與每個程序計數器相關聯。 第二個將“緩存未命中數”值與每個程序計數器相關聯。
注意:我經常“減少”數據,按功能或(如果我有技術)按循環總結。 或者按 64 個字節的 bin。 比較單個程序計數器通常沒有用,因為性能計數器是模糊的 - 您看到緩存未命中報告的地方通常是與實際發生的地方不同的幾條指令。
好的,現在繪制這兩個配置文件來比較它們。 以下是我覺得有用的一些圖表:
“冰山”圖表:X 軸是 PC,正 Y 軸是時間,負 Y 軸是緩存未命中。 尋找可以上升和下降的地方。
(“交錯”圖表也很有用:同樣的想法,X 軸是 PC,在 Y 軸上繪制時間和緩存未命中,但具有不同顏色的窄垂直線,通常為紅色和藍色。時間和緩存都很多的地方花費的未命中將有精細交錯的紅線和藍線,幾乎看起來是紫色的。這延伸到 L2 和 L3 緩存未命中,都在同一張圖上。順便說一句,您可能想要“標准化”這些數字,要么是總數的百分比時間或緩存未命中,或者更好的是,最大數據時間點或緩存未命中的百分比。如果比例錯誤,您將看不到任何內容。)
XY 圖表:對於每個采樣箱(PC、函數、循環或...)繪制一個點,其X 坐標是歸一化時間,其 Y 坐標是歸一化緩存未命中。 如果您在右上角獲得大量數據點 - 大 %age 時間和大 %age 緩存未命中 - 這就是有趣的證據。 或者,忘記點數 - 如果上角所有百分比的總和很大......
請注意,不幸的是,您經常必須自己進行這些分析。 最后我檢查了 VTune 不適合你。 我用過 gnuplot 和 Excel。 (警告:Excel 在超過 64,000 個數據點時死亡。)
更多建議:
如果你的智能指針是內聯的,你可能會得到到處都是計數。 在理想的世界中,您將能夠將 PC 追溯到源代碼的原始行。 在這種情況下,您可能需要稍微推遲減少:查看所有個人電腦; 將它們映射回源代碼行; 然后將它們映射到原始函數中。 許多編譯器,例如 GCC,都有符號表選項允許您執行此操作。
順便說一句,我懷疑您的問題不在於導致緩存抖動的智能指針。 除非你到處都在做 smart_ptr<int> 。 如果您正在執行 smart_ptr<Obj>,並且 sizeof(Obj) + 大於 4*sizeof(Obj*)(並且如果 smart_ptr 本身不是很大),那么它並沒有那么多。
更有可能是智能指針執行的額外間接級別導致了您的問題。
巧合的是,我在午餐時和一個人談話,他有一個使用句柄的引用計數智能指針,即一個間接級別,類似於
template<typename T> class refcntptr {
refcnt_handle<T> handle;
public:
refcntptr(T*obj) {
this->handle = new refcnt_handle<T>();
this->handle->ptr = obj;
this->handle->count = 1;
}
};
template<typename T> class refcnt_handle {
T* ptr;
int count;
friend refcnt_ptr<T>;
};
(我不會這樣編碼,但它用於說明。)
雙重間接this->handle->ptr可能是一個很大的性能問題。 甚至是三重間接尋址,this->handle->ptr->field。 至少,在具有 5 個循環 L1 緩存命中的機器上,每個 this->handle->ptr->field 將花費 10 個循環。 並且比單個指針追逐更難重疊。 但是,更糟糕的是,如果每個都是 L1 緩存未命中,即使到 L2 僅 20 個周期……嗯,隱藏 2*20=40 個緩存未命中延遲周期比單個 L1 未命中要困難得多。
一般來說,避免智能指針中的間接級別是一個很好的建議。 不是指向一個句柄,所有智能指針都指向一個句柄,它本身指向對象,你可以通過讓它指向對象和句柄來使智能指針更大。 (這不再是通常所說的句柄,而更像是一個信息對象。)
例如
template<typename T> class refcntptr {
refcnt_info<T> info;
T* ptr;
public:
refcntptr(T*obj) {
this->ptr = obj;
this->info = new refcnt_handle<T>();
this->info->count = 1;
}
};
template<typename T> class refcnt_info {
T* ptr; // perhaps not necessary, but useful.
int count;
friend refcnt_ptr<T>;
};
無論如何 - 時間檔案是您最好的朋友。
哦,是的 - 英特爾 EMON 硬件還可以告訴您在 PC 上等待了多少個周期。 這可以區分大量 L1 未命中和少量 L2 未命中。
如果您運行的是 AMD 處理器,則可以獲得CodeAnalyst ,顯然是免費的,就像在啤酒中一樣。
我的建議是使用英特爾的PTU (性能調優實用程序)。
此實用程序是 VTune 的直接后代,並提供可用的最佳采樣分析器。 您將能夠跟蹤 CPU 花費或浪費時間的位置(借助可用的硬件事件),並且這不會降低您的應用程序或配置文件的干擾。 當然,您將能夠收集您正在尋找的所有緩存行未命中事件。
另一個基於 CPU 性能計數器的分析工具是oprofile 。 您可以使用 kcachegrind 查看其結果。
這是一個通用的答案。
例如,如果您的程序花費了 50% 的時間在緩存未命中上,那么當您暫停它時,程序計數器有 50% 的時間將位於它正在等待導致內存獲取的確切位置緩存未命中。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.