Cachegrind:缓存和分支预测分析器
目錄
5.1。概觀5.2。使用Cachegrind,cg_annotate和cg_merge要使用此工具,必須--tool=cachegrind在Valgrind命令行上指定?。
5.1。概觀
Cachegrind模擬程序如何與機器的緩存層次結構和(可選)分支預測器進行交互。它模擬具有獨立的第一級指令和數據高速緩存(I1和D1)的機器,由統一的二級緩存(L2)支持。這完全符合許多現代機器的配置。
然而,一些現代機器具有三或四級緩存。對于這些機器(在Cachegrind可以自動檢測緩存配置的情況下)Cachegrind模擬一級和最后一級緩存。這個選擇的原因是最后一級緩存對運行時的影響最大,因為它掩蓋了對主內存的訪問。此外,L1高速緩存通常具有低關聯性,因此模擬它們可以檢測代碼與該高速緩存交互不良的情況(例如,以行長為2的行列列逐列)。
因此,Cachegrind總是引用I1,D1和LL(最后一級)緩存。
Cachegrind收集以下統計信息(括號中給出每個統計量使用的縮寫):
-
我緩存讀取(Ir等于執行的指令數),I1緩存讀取未命中(I1mr)和LL緩存指令讀取misses(ILmr)。
-
D緩存讀取(Dr等于內存讀取次數),D1緩存讀取未命中(D1mr)和LL緩存數據讀取misses(DLmr)。
-
D緩存寫入(Dw等于存儲器寫入數),D1緩存寫入未命中(D1mw)和LL緩存數據寫入未命中(DLmw)。
-
條件分支執行(Bc)和條件分支mispredicted(Bcm)。
-
間接分支執行(Bi)和間接分支錯誤(Bim)。
請注意,D1總訪問由D1mr+?給出?D1mw,LL總訪問由ILmr+?DLmr+?給出DLmw。
為整個程序和程序中的每個功能提供這些統計信息。您還可以使用直接導致的計數來注釋程序中的每一行源代碼。
在現代機器上,L1故障通常會花費大約10個周期,LL錯誤可能花費多達200個周期,并且在10到30個周期的區域中,錯誤的分支成本。詳細的緩存和分支分析可以非常有助于了解您的程序如何與機器交互,從而如何使其更快。
另外,由于每個執行指令都執行了一條指令高速緩存讀取,您可以找出每行執行的指令數,這對于傳統的分析來說是有用的。
5.2。使用Cachegrind,cg_annotate和cg_merge
首先,對于正常的Valgrind使用,您可能希望使用調試信息(-g選項)編譯?。但是與正常的Valgrind使用相比,您可能想要優化,因為您應該對程序進行配置,因為它將正常運行。
然后,您需要運行Cachegrind本身來收集分析信息,然后運行cg_annotate來獲取該信息的詳細介紹。作為可選的中間步驟,您可以使用cg_merge將多個Cachegrind運行的輸出合并到一個文件中,然后將其用作cg_annotate的輸入?;蛘?#xff0c;您可以使用cg_diff將兩個Cachegrind運行的輸出區分為一個文件,然后將其用作cg_annotate的輸入。
5.2.1。運行Cachegrind
要在程序上運行Cachegrind?prog,請運行:
valgrind --tool = cachegrind prog程序將執行(緩慢)。完成后,將打印如下的摘要統計信息:
== 31751 ==我參考:27,742,716 == 31751 == I1 misses:276 == 31751 == LLi misses:275 == 31751 == I1 miss rate:0.0% == 31751 == LLi miss rate:0.0% == == 31751 == 31751 == D refs:15,430,290(10,955,517 rd + 4,474,773 wr) == 31751 == D1 misses:41,185(21,905 rd + 19,280 wr) == 31751 == LLd misses:23,085(3,987 rd + 19,098 wr) == 31751 == D1失誤率:0.2%(0.1%+ 0.4%) == 31751 ==差錯率:0.1%(0.0%+ 0.4%) == == 31751 == 31751 == LL misses:23,360(4,262 rd + 19,098 wr) == 31751 == LL錯失率:0.0%(0.0%+ 0.4%)首先總結指令讀取的高速緩存訪??問,給出取得的數量(這是執行的指令數,這可以用于知道自己的權限),I1的次數以及LL指令的數量(LLi)未命中。
緩存訪問數據跟隨。信息類似于指令提取的信息,除了這些值還顯示在讀取和寫入之間分開(注意每行?rd和?wr值都加在行的總數上)。
LL緩存的組合指令和數據數據就是這樣。注意,相對于存儲器訪問的總數而不是L1未命中的數量來計算LL錯誤率。也就是?(ILmr + DLmr + DLmw) / (Ir + Dr + Dw)?不是?(ILmr + DLmr + DLmw) / (I1mr + D1mr + D1mw)
默認情況下不收集分支預測統計信息。為此,請添加該選項--branch-sim=yes。
5.2.2。輸出文件
除了打印摘要信息,Cachegrind還將更詳細的剖析信息寫入文件。默認情況下,該文件被命名?cachegrind.out.<pid>(<pid>程序的進程ID?在哪里?),但可以使用該--cachegrind-out-file選項更改其名稱。該文件是人類可讀的,但意圖由下一節中描述的隨附程序cg_annotate解釋。
.<pid>輸出文件名中的默認后綴有兩個目的。首先,這意味著您不必重命名不想覆蓋的舊日志文件。其次,更重要的是,它允許使用--trace-children=yes生成子進程的程序選項進行正確的?分析。
輸出文件可以是大的,對于大量應用程序構建完整調試信息的兆字節數。
5.2.3。運行cg_annotate
在使用cg_annotate之前,如果可能,擴展窗口至少應該是120個字符,因為輸出行可能相當長。
要獲取功能函數摘要,請運行:
cg_annotate <filename>在Cachegrind輸出文件上。
5.2.4。輸出序言
輸出的第一部分如下所示:
-------------------------------------------------- ------------------------------ I1緩存:65536 B,64 B,雙向關聯 D1緩存:65536 B,64 B,雙向關聯 LL緩存:262144 B,64 B,8路關聯 命令:concord vg_to_ucode.c 事件記錄:Ir I1mr ILmr Dr D1mr DLmr Dw D1mw DLmw 事件顯示:Ir I1mr ILmr Dr D1mr DLmr Dw D1mw DLmw 事件排序順序:Ir I1mr ILmr Dr D1mr DLmr Dw D1mw DLmw 門檻值:99% 選擇注釋: 自動注釋:關閉這是注釋選項的摘要:
-
I1緩存,D1緩存,LL緩存:緩存配置。所以你知道獲得這些結果的配置。
-
命令:命令行調用正在檢查的程序。
-
記錄的事件:記錄了哪些事件。
-
顯示的事件:顯示的事件,這是收集的事件的一個子集。這可以通過--show選項進行調整?。
-
事件排序順序:顯示函數的排序順序。例如,在這種情況下,功能將從最高Ir計數排序到最低。如果兩個函數具有相同的Ir計數,則它們將按?計數排序I1mr,依此類推。可以使用--sort選項調整此順序?。
請注意,這表示函數出現的順序。這不是列出現的順序。這是由“顯示的事件”行(可以隨--show?選項更改)所規定的。
-
閾值:默認情況下cg_annotate忽略導致非常低計數的功能,以避免淹沒您的信息。在這種情況下,cg_annotate顯示了占Ir計數的99%的函數的摘要;?Ir被選為閾值事件,因為它是主要排序事件??梢允褂?-threshold?選項調整閾值?。
-
選擇注釋:手動指定用于注釋的文件的名稱;?在這種情況下沒有。
-
自動注釋:是否通過--auto=yes?選項請求自動注釋。在這種情況下沒有。
5.2.5。全局和功能級別計數
然后按照整個程序的總結統計:
-------------------------------------------------- ------------------------------ Irlmr ILmr Dr D1mr DLmr Dw D1mw DLmw -------------------------------------------------- ------------------------------ 27,742,716 276 275 10,955,517 21,905 3,987 4,474,773 19,280 19,098計劃總額這些與Cachegrind完成運行時提供的摘要類似。
然后來函數統計:
-------------------------------------------------- ------------------------------ Irlmr ILmr Dr D1mr DLmr Dw D1mw DLmw文件:功能 -------------------------------------------------- ------------------------------ 8,821,482 5 5 2,242,702 1,621 73 1,794,230 0 0 getc.c:_IO_getc 5,222,023 4 4 2,276,334 16 12 875,959 1 1 concord.c:get_word 2,649,248 2 2 1,344,810 7,326 1,385。。。vg_main.c:STRCMP 2,521,927 2 2 591,215 0 0 179,398 0 0 concord.c:哈希 2,242,740 2 2 1,046,612 568 22 448,548 0 0 ctype.c:tolower 1,496,937 4 4 630,874 9,000 1,400 279,388 0 0 concord.c:insert897,991 51 51 897,831 95 30 62 1 1 ???598,068 1 1 299,034 0 0 149,517 0 0 ../sysdeps/generic/lockfile.c:__flockfile598,068 0 0 299,034 0 0 149,517 0 0 ../sysdeps/generic/lockfile.c:__funlockfile598,024 4 4 213,580 35 16 149,506 0 0 vg_clientmalloc.c:malloc446,587 1 1 215,973 2,167 430 129,948 14,057 13,957 concord.c:add_existing341,760 2 2 128,160 0 0 128,160 0 0 vg_clientmalloc.c:vg_trap_here_WRAPPER320,782 4 4 150,711 276 0 56,027 53 53 concord.c:init_hash_table298,998 1 1 106,785 0 0 64,071 1 1 concord.c:create149,518 0 0 149,516 0 0 1 0 0 ???:tolower @@ GLIBC_2.0149,518 0 0 149,516 0 0 1 0 0 ????:fgetc @@ GLIBC_2.095,983 4 4 38,031 0 0 34,409 3,152 3,150 concord.c:new_word_node85,440 0 0 42,720 0 0 21,360 0 0 vg_clientmalloc.c:vg_bogus_epilogue每個功能由一file_name:function_name對標識?。如果列只包含一個點,則表示該函數不會執行該事件(例如,第三行顯示不?strcmp()包含寫入內存的指令)。這個名字????是用來如果文件名稱和/或函數的名稱無法從調試信息來確定。如果大多數條目具有該???:???程序可能未被編譯的形式?-g。
值得注意的是,功能將來自concord.c于類別程序(例如)和庫(例如getc.c)
5.2.6。逐行計數
有兩種注釋源文件的方法 - 通過將其手動指定為cg_annotate的參數或使用該?--auto=yes選項。例如,cg_annotate <filename> concord.c對于我們的示例,運行的輸出?產生與上述相同的輸出,后跟一個注釋版本?concord.c,其中的一部分看起來像:
-------------------------------------------------- ------------------------------ - 用戶注釋源:concord.c -------------------------------------------------- ------------------------------ Irlmr ILmr Dr D1mr DLmr Dw D1mw DLmw。。。。。。。。。void init_hash_table(char * file_name,Word_Node * table [])3 1 1。。。1 0 0 {。。。。。。。。。FILE * file_ptr;。。。。。。。。。Word_Info *數據;1 0 0。。。1 1 1 int line = 1,i;。。。。。。。。。5 0 0。。。3 0 0 data =(Word_Info *)create(sizeof(Word_Info));。。。。。。。。。(i = 0; i <TABLE_SIZE; i ++)4,991 0 0 1,995 0 0 998 0 03,988 1 1 1,994 0 0 997 53 52 table [i] = NULL;。。。。。。。。。。。。。。。。。。/ *打開文件,檢查。* /6 0 0 1 0 0 4 0 0 file_ptr = fopen(file_name,“r”);2 0 0 1 0 0。。。if(!(file_ptr)){。。。。。。。。。fprintf(stderr,“無法打開'%s'。\ n”,file_name);1 1 1。。。。。。出口(EXIT_FAILURE);。。。。。。。。。}。。。。。。。。。165,062 1 1 73,360 0 0 91,700 0 0 while((line = get_word(data,line,file_ptr))!= EOF)146,712 0 0 73,356 0 0 73,356 0 0 insert(data - >; word,data-> line,table);。。。。。。。。。4 0 0 1 0 0 2 0 0免費(數據);4 0 0 1 0 0 2 0 0 fclose(file_ptr);3 0 0 2 0 0。。。}(盡管柱寬度自動最小化,但廣泛的終端顯然是有用的。)
每個源文件被清楚地標記為(User-annotated source)被手動選擇用于注釋。如果在-I/?--includeoptions中指定的目錄之一中找到該文件?,則會同時給出目錄和文件。
每一行都用事件計數進行注釋。不適用于一行的事件由點表示。這對于區分不能發生的事件和可以但不能發生的事件是有用的。
有時只執行一小部分源文件。為了最小化不感興趣的輸出,Cachegrind僅在注釋行的一小段距離內顯示注釋行和行。間隙標有行號,以便您知道顯示的代碼來自哪個文件的哪個部分,例如:
(線704的圖和代碼) - 第704行---------------------------------------- - 878行---------------------------------------- (第878行的數字和代碼)在注釋行顯示的上下文的數量由--context?選項控制。
要獲取自動注釋,請使用該--auto=yes選項。cg_annotate會自動注釋每個函數摘要中提到的每個源文件。因此,為自動注釋選擇的文件會受到--sort和?--threshold選項的影響。每個源文件被清楚地標記為(Auto-annotated source)被自動選擇。在輸出結尾處提到無法找到的任何文件,例如:
-------------------------------------------------- ---------------- 找不到以下選擇自動注釋的文件: -------------------------------------------------- ----------------getc.cctype.c../sysdeps/generic/lockfile.c這對于庫文件是很常見的,因為庫通常使用調試信息進行編譯,但源文件通常不在系統中。如果選擇了手動和自動注釋的文件,則將其標記為User-annotated source。如果從調試信息中找到的文件名不夠詳細,請使用?-I/?--include選項告訴Valgrind尋找源文件的位置。
請注意,cg_annotate可能需要一些時間來消化大?cachegrind.out.<pid>文件,例如30秒或更長。還要注意,如果您的程序很大,自動注釋可以產生大量的輸出!
5.2.7。注釋匯編代碼程序
Valgrind也可以注釋匯編代碼程序,也可以注釋為C程序生成的匯編代碼。有時,這有助于了解當有趣的C代碼行被轉換成多個指令時,真正發生了什么。
為此,您只需要.s使用程序集級調試信息來匯編?文件。您可以使用compile將-SC / C ++程序編譯為匯編代碼,然后組裝匯編代碼文件?-g以實現此目的。然后,您可以以與C / C ++源文件相同的方式對匯編代碼源文件進行配置和注釋。
5.2.8。分岔程序
如果您的程序分叉,該子將繼承所有為父級收集的分析數據。
如果輸出文件格式字符串(受控制?--cachegrind-out-file)不包含%p,則父和子的輸出將混合在單個輸出文件中,這幾乎肯定會使其不能被cg_annotate讀取。
5.2.9。cg_annotate警告
有幾種情況,cg_annotate發出警告。
-
如果源文件比?cachegrind.out.<pid>文件更新。這是因為信息僅以?cachegrind.out.<pid>行號記錄,因此如果行號在源中完全變化(例如添加,刪除,交換的行),任何注釋將不正確。
-
如果記錄關于文件末尾的行號的信息。這可能是由于上述問題引起的,即在使用舊cachegrind.out.<pid>文件時縮短源?文件。如果發生這種情況,虛線的數字打印(清楚地標記為假),以防重要。
5.2.10。異常注釋案例
在注釋期間可能發生的一些奇怪的事情:
-
如果在匯編級別注釋,您可能會看到如下:
1 0 0。。。。。。leal -12(%ebp),%eax1 0 0。。。1 0 0 movl%eax,84(%ebx)2 0 0 0 0 0 1 0 0 movl $ 1,-20(%ebp)。。。。。。。。。.align 4,0x901 0 0。。。。。。movl $ .LnrB,%eax1 0 0。。。1 0 0 movl%eax,-16(%ebp)當其他人只執行一次時,如何執行第三條指令兩次?事實證明,不是。這是可執行文件的轉儲,使用?objdump -d:
8048f25:8d 45 f4 lea 0xfffffff4(%ebp),%eax8048f28:89 43 54 mov%eax,0x54(%ebx)8048f2b:c7 45 ec 01 00 00 00 movl $ 0x1,0xffffffec(%ebp)8048f32:89 f6 mov%esi,%esi8048f34:b8 08 8b 07 08 mov $ 0x8078b08,%eax8048f39:89 45 f0 mov%eax,0xfffffff0(%ebp)注意額外的mov %esi,%esi說明。這是從哪里來的?GNU匯編器將其插入作為movl $.LnrB,%eax四字節邊界對齊指令所需的兩個字節的填充,但假設它在添加調試信息時不存在。因此,當Valgrind讀取調試信息時,它認為該movl $0x1,0xffffffec(%ebp)指令本身覆蓋地址范圍0x8048f2b - 0x804833,并將其計數歸因mov %esi,%esi于它。
-
有時,相同的文件名可能用相對名稱表示,并且在調試信息的不同部分中使用絕對名稱,例如:?/home/user/proj/proj.h和?../proj.h。在這種情況下,如果您使用自動注釋,則文件將被注釋兩次,并在兩者之間進行分割。
-
如果你編譯一些沒有的文件?-g,一些在沒有調試信息的文件中發生的事件可以歸因于具有調試信息的文件的最后一行(無論哪個文件放在可執行文件中的非調試信息文件之前) )。
這個列表看起來很長,但是這些例子應該是相當罕見的。
5.2.11。將配置文件與cg_merge合并
cg_merge是一個簡單的程序,它讀取由Cachegrind創建的多個配置文件,將它們合并在一起,并以相同的格式將結果寫入另一個文件。然后,您可以使用cg_annotate <filename>如上所述來檢查合并的結果?。如果您希望通過同一程序的多次運行來匯總成本,或者從具有相同程序的多個實例的單個并行運行中合并功能可能會很有用。
cg_merge被調用如下:
cg_merge -o outputfile file1 file2 file3 ...它讀取和檢查file1,然后讀取和檢查file2并將其合并到運行總計中,然后與file3等同等?。最終結果將寫入outputfile或標準輸出,如果沒有指定輸出文件。
成本按每個功能,每行和每個指令進行總結。因為這樣,輸入文件的順序不重要,雖然你應該注意只提一次文件,因為提到的兩個文件將被添加兩次。
cg_merge不會嘗試檢查輸入文件是否來自相同可執行文件的運行。它將很樂意將來自完全不相關程序的配置文件合并在一起。然而,它檢查Events:所有輸入的?線條是否相同,以確保增加成本是有意義的。例如,將一個數字表示為從一個指定LL寫入未命中的不同文件中的一個數字的數字的數字將是無意義的。
在閱讀輸入時,還會完成許多其他語法和理性檢查。如果任何輸入文件失敗,cg_merge將停止并嘗試打印有用的錯誤消息。
5.2.12。帶cg_diff的差異配置文件
cg_diff是一個簡單的程序,它讀取由Cachegrind創建的兩個配置文件,找到它們之間的區別,并以相同的格式將結果寫入另一個文件。然后,您可以使用cg_annotate <filename>如上所述來檢查合并的結果?。如果您想衡量一個程序的更改如何影響其性能,這是非常有用的。
cg_diff被調用如下:
cg_diff file1 file2它讀取和檢查file1,然后讀取和檢查file2,然后計算差異(有效file1-?file2)。最終結果寫入標準輸出。
費用是按功能計算的。每行費用并不總計,因為這樣做太難了。例如,考慮區分兩個配置文件,一個來自單個文件程序A,另一個來自同一程序A,其中在文件頂部插入單個空行。每一行一行計數都發生了變化。相比之下,每個功能的計數沒有改變。每個函數的計數差異對于確定程序之間的差異仍然非常有用。注意,因為結果是兩個輪廓的差異,許多計數將為負數;?這表示第二版中相關功能的計數少于第一版中的計數。
cg_diff不會嘗試檢查輸入文件是否來自相同可執行文件的運行。它將很樂意將來自完全不相關程序的配置文件合并在一起。然而,它檢查Events:所有輸入的?線條是否相同,以確保增加成本是有意義的。例如,將一個數字表示為從一個指定LL寫入未命中的不同文件中的一個數字的數字的數字將是無意義的。
在閱讀輸入時,還會完成許多其他語法和理性檢查。如果任何輸入文件失敗,cg_diff將停止并嘗試打印有用的錯誤消息。
有時你會想要比較并排坐的兩個版本的程序的Cachegrind配置文件。例如,你可能有?version1/prog.c和?version2/prog.c,其中第二個是第一個稍有不同。兩者的直接比較將不是有用的 - 因為函數使用文件名進行限定,所以第一個版本?的功能?f將被列為?第二個版本。version1/prog.c:fversion2/prog.c:f
發生這種情況時,可以使用該--mod-filename選項。它的參數是Perl搜索和替換表達式,它將應用于Cachegrind輸出文件中的所有文件名。它可用于刪除文件名中的微小差異。例如,這個選項?--mod-filename='s/version[0-9]/versionN/'就足夠了。
類似地,有時編譯器自動生成某些功能并給它們隨機化的名稱。例如,GCC有時會自動生成名稱類似的函數,T.1234后綴因構建而異。您可以使用該--mod-funcname選項來刪除像這樣的小差異;?它的工作方式與?--mod-filename。
5.3。Cachegrind命令行選項
Cachegrind特定的選項是:
--I1=<size>,<associativity>,<line size>指定級別1指令高速緩存的大小,關聯度和行大小。
指定1級數據緩存的大小,關聯度和行大小。
指定最后一級緩存的大小,關聯性和行大小。
啟用或禁用緩存訪問和錯誤計數的收集。
啟用或禁用分支指令和錯誤預測計數的收集。默認情況下,這是禁用的,因為Cachegrind下降了大約25%。請注意,您無法指定--cache-sim=no?并--branch-sim=no?在一起,因為這將使Cachegrind無法收集信息。
將配置文件數據寫入?file而不是默認的輸出文件?cachegrind.out.<pid>。的?%p和%q格式說明可以用來嵌入進程ID和/或名稱的環境變量的內容,由于是用于芯選項的情況下--log-file。
5.4。cg_annotate命令行選項
-h --help顯示幫助信息。
顯示版本號。
指定要顯示的事件(和列順序)。默認是使用cachegrind.out.<pid>文件中的所有?文件(并使用文件中的順序)。如果你想集中精力,比如我緩存misses(--show=I1mr,ILmr)或數據讀取misses(--show=D1mr,DLmr)或LL數據丟失(--show=DLmr,DLmw)。最適合使用?--sort。
指定逐個功能條目排序的事件。
設置函數功能摘要的閾值。如果占用主排序事件的計數的X%以上,則顯示一個函數。如果自動注釋,還會影響哪些文件被注釋。
注意:可以為多個事件設置閾值,方法是附加--sort冒號和數字的任何事件?(不含空格)。例如,如果您想查看涵蓋超過1%LL讀取錯誤或1%LL寫入未命中的每個功能,請使用此選項:
--sort=DLmr:1,DLmw:1
啟用后,會自動注釋可以找到的逐個功能摘要中提到的每個文件。還列出了無法找到的列表。
在每個注釋行之前和之后打印N行上下文。避免打印未執行的大部分源文件。使用大量(例如100000)顯示所有源行。
將目錄添加到搜索文件的列表中。多-I/?--include?選項可以給添加多個目錄。
5.5。cg_merge命令行選項
-o outfile將配置文件數據寫入outfile?而不是標準輸出。
5.6。cg_diff命令行選項
-h --help顯示幫助信息。
顯示版本號。
指定應用于所有文件名的Perl搜索和替換表達式。用于消除位于不同目錄的程序的兩個不同版本之間的路徑中的小差異。
喜歡--mod-filename,但為文件名。有助于消除一些編譯器生成的自動生成函數的隨機化名稱中的微小差異。
5.7。代表Cachegrind的信息
Cachegrind給你很多信息,但是對這些信息的處理并不總是容易的。以下是我們發現有用的一些經驗法則。
首先,全球命中/錯失次數和錯失率并不那么有用。如果您有多個程序或多個程序運行,則比較數字可能會識別是否有異常值并值得進行仔細調查。否則,他們還不夠行事。
查看功能的功能計數更有用,因為它們確定哪些功能導致大量計數。但是,請注意,內聯可以使這些計數產生誤導。如果函數?f總是內聯,則計數將歸因于其內聯的功能,而不是其本身。但是,如果您查看逐行注釋,f您將看到屬于的計數f。(這是很難避免的,這是調試信息的結構。)所以值得尋找大量的逐行注釋。
逐行源代碼注釋更加有用。根據我們的經驗,最好的開始是看?Ir數字。它們簡單地測量每行執行多少指令,并且不包括任何緩存信息,但是它們仍然可以用于識別瓶頸。
之后,我們發現,LL錯誤通常是比L1錯失更大的減速來源。所以值得尋找任何高代碼DLmr或?代碼片段DLmw。(例如,您可以使用?--show=DLmr --sort=DLmrcg_annotate來重點關注數字?DLmr。)如果找到任何內容,那么解決問題仍然不容易。您需要對緩存的工作原理,本地原則以及程序的數據訪問模式有一個合理的了解。改進事情可能需要重新設計數據結構。
看著Bcm和?Bim錯過也可以是有幫助的。特別地,Bim錯誤通常是由switch語句引起的,在某些情況下,這些?switch語句可以被表驅動代碼替代。例如,您可能會替換如下代碼:
枚舉E {A,B,C}; 枚舉E e int i ... 開關(e) {情況A:i + = 1; 打破;情況B:i + = 2; 打破;情況C:i + = 3; 打破; }代碼如下:
枚舉E {A,B,C}; 枚舉E e 枚舉E表[] = {1,2,3}; int i ... i + =表[e];這顯然是一個例證,但基本原則適用于各種情況。
簡而言之,Cachegrind可以告訴你代碼中的一些瓶頸,但是它不能告訴你如何解決它們。你必須為自己工作。但至少你有信息!
5.8。模擬細節
本節將介紹您不需要了解的細節,以便使用Cachegrind,但有些人可能會感興趣。
5.8.1。緩存模擬細節
緩存模擬的具體特點如下:
-
寫分配:當寫入未命中時,寫入的塊被帶入D1高速緩存。大多數現代高速緩存有此屬性。
-
位選擇散列函數:由字節地址的中間位M - (M + N-1)選擇存儲器塊映射到的高速緩存中的行集合,其中:
-
行大小= 2 ^ M字節
-
(高速緩存大小/行大小/關聯性)= 2 ^ N個字節
-
-
包含LL緩存:LL緩存通常復制L1高速緩存的所有條目,因為取入L1涉及首先提取LL(這不保證嚴格的包容性,因為從LL中移除的行仍然可以駐留在L1中)。這是Pentium芯片的標準配置,但是AMD Opterons,Athlons和Durons使用的唯一LL緩存僅保留從L1驅逐的塊。同時也是最現代的VIA CPU。
使用x86 CPUID指令自動確定模擬的緩存配置(緩存大小,關聯性和行大小)。如果您有一臺機器,(a)不支持CPUID指令,或(b)在不提供任何緩存信息的早期化身中支持,那么Cachegrind將回退到使用默認配置3/4速龍)。Cachegrind會告訴你是否發生這種情況??梢允謩又付ǖ拿钚械母咚倬彺嬷械囊粋€,兩個或所有三個級別(I1 / D1 / LL)使用?--I1,?--D1和?--LL選項。對于緩存參數對模擬有效,集合的數量(每個組中的高速緩存行數量的關聯性)必須是2的冪。
在PowerPC平臺Cachegrind不能自動確定緩存配置,所以你將需要與指定它?--I1,?--D1和?--LL選項。
其他值得注意的行為:
-
跨越兩條緩存行的引用如下處理:
-
如果兩個塊都擊中 - >計數為一個命中
-
如果一個塊命中,另一個錯過 - >算作一個小姐。
-
如果兩個塊都錯過 - >計數為一個錯過(不是兩個)
-
-
修改存儲器位置(例如inc和?dec)的指令被視為僅僅讀取,即單個數據引用。這可能看起來很奇怪,但是由于寫入永遠不會導致錯過(讀取保證塊位于緩存中),這并不是很有趣。
因此,它不會測量數據高速緩存被訪問的次數,而是數據高速緩存未命中的次數。
如果您有興趣模擬具有不同屬性的緩存,編寫自己的緩存模擬器或修改現有緩存模擬器并不是特別難?cg_sim.c。我們有興趣從任何人那里聽到。
5.8.2。分支模擬細節
Cachegrind模擬了大概在2004年左右的主流桌面/服務器處理器的分支預測器。
使用16384個2位飽和計數器的數組來預測條件分支。用于分支指令的數組索引部分地由分支指令的地址的低位進行計算,并且部分地使用最后幾個條件分支的采取/未采取的行為。因此,任何具體分支的預測都取決于它自己的歷史和以前分支的行為。這是提高預測精度的標準技術。
對于間接分支(即跳轉到未知目的地),Cachegrind使用一個簡單的分支目標地址預測器。使用由分支指令地址的低位9位索引的512個條目的數組預測目標。預計每個分支將跳到與上次相同的地址。任何其他行為都會導致錯誤預測。
最近的處理器具有更好的分支預測因子,特別是更好的間接分支預測器。Cachegrind的預測器設計是故意保守的,以便代表大型安裝的處理器的基礎,預先廣泛部署更復雜的間接分支預測器。具體來說,晚期型號Pentium 4(Prescott),Pentium M,Core和Core 2具有比Cachegrind建模的更復雜的間接分支預測器。
Cachegrind不會模擬一個返回棧預測器。它假設處理器完美地預測函數返回地址,這個假設可能接近于true。
參見軒尼詩和帕特森的經典文本“計算機體系結構:定量方法”,第4版(2007),第2.3節(第80-89頁),用于現代分支預測器的背景。
5.8.3。準確性
Valgrind的緩存分析有一些缺點:
-
它不考慮內核活動 - 忽略系統調用對緩存和分支預測器內容的影響。
-
它不考慮其他流程活動??紤]單個程序時,這可能是可取的。
-
它不考慮虛擬到物理地址映射。因此,模擬并不是緩存中發生的事情的真實表示。大多數緩存和分支預測器都被物理索引,但Cachegrind使用虛擬地址來模擬緩存。
-
它不考慮在指令級別不可見的高速緩存未命中,例如由TLB未命中或者推測執行引起的緩存未命中。
-
Valgrind將安排線程與本機運行時的方式不同。這可能會扭曲線程程序的結果。
-
在x86 / AMD64指令bts,?btr并且?btc將錯誤地被計算為這樣做,如果雙方的觀點是寄存器,例如讀取數據:
btsl%eax,%edx這只會很少發生。
-
數據大小為28和108字節(例如fsave)的x86 / amd64 FPU指令?被視為只能訪問16個字節。這些說明似乎是罕見的,希望這不會影響準確性。
值得注意的另一件事是,結果非常敏感。更改正在分析的可執行文件的大小,或其使用的任何共享庫的大小,甚至文件名的長度可能會影響結果。變化會很小,但如果您的程序發生變化,不要指望完美的重復性結果。
更新近的GNU / Linux發行版將解決空間隨機化問題,其中相同程序的相同運行將其共享庫加載到不同位置,作為安全措施。這也擾亂了結果。
雖然這些因素意味著您不應該將結果信任為超級準確,但應該足夠接近有用。
5.9。實施細節
本節將介紹您不需要了解的細節,以便使用Cachegrind,但有些人可能會感興趣。
5.9.1。Cachegrind如何工作
了解Cachegrind如何工作的最佳參考是Nicholas Nethercote的“動態二進制分析和儀器”第3章。它可在Valgrind出版物頁面上找到。
5.9.2。Cachegrind輸出文件格式
文件格式相當簡單,基本上是給每個行的成本中心,按文件和功能分組。它也是完全一般的和自我描述的,因為它可以用于可以逐行計算的任何事件,而不僅僅是緩存和分支預測器事件。例如,早期版本的Cachegrind沒有分支預測器模擬。添加此文件后,文件格式無需更改。所以格式(和因此,cg_annotate)可以被其他工具使用。
文件格式:
file :: = desc_line * cmd_line events_line data_line + summary_line desc_line :: =“desc:”ws?non_nl_string cmd_line :: =“cmd:”ws?CMD events_line :: =“events:”ws?(事件ws)+ data_line :: = file_line | fn_line | count_line file_line :: =“fl =”filename fn_line :: =“fn =”fn_name count_line :: = line_num ws?(計數ws)+ summary_line :: =“summary:”ws?(計數ws)+ count :: = num | “”哪里:
-
non_nl_string?是不包含換行符的任何字符串。
-
cmd?是一個保存分析程序的命令行的字符串。
-
event?是一個不包含空格的字符串。
-
filename并且?fn_name是字符串。
-
num并且?line_num是十進制數。
-
ws?是空白。
“desc:”行的內容打印在摘要的頂部。這是提供模擬特定信息的通用方式,例如為高速緩存模擬提供高速緩存配置。
可以為每個文件/ fn /行號顯示多行信息。在這種情況下,命名事件的計數將被累積。
計數可以是“”。代表零。這使得文件更容易讓人閱讀。
在每個計數的數量?line和?summary_line不應超過在事件的數量?event_line。如果每個數字line較少,cg_annotate會將那些丟失的對象視為“?!?。條目。這節省了空間。
A?file_line更改當前文件名。A?fn_line?更改當前函數名稱。A?count_line包含與當前文件名/ fn_name相關的計數。一個“fn =”?file_line,?fn_line必須出現在任何count_lines?之前?給出第一個count_lines?的上下文。
每個file_line通常會緊隨其后的是a?fn_line。但它不一定是。
摘要行是多余的,因為它只保留每個事件的總計數。但這是對數據的有益的健康檢查;?如果每個事件的總計與摘要行不匹配,則出現了錯誤。
總結
以上是生活随笔為你收集整理的Cachegrind:缓存和分支预测分析器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于vue的颜色选择器vue-color
- 下一篇: 如何使用strace+pstack利器分