perf 性能分析实例——使用perf优化cache利用率
摘要:本文主要講解如何使用perf觀察程序在緩存利用方面的瓶頸,進而優(yōu)化程序,提高cache命中率。主要講解提高緩存利用的幾種常用方法。
1.程序局部性
一個編寫良好的計算機程序通常具有程序的局部性,它更傾向于引用最近引用過的數(shù)據(jù)項,或者這個數(shù)據(jù)周圍的數(shù)據(jù)——前者是時間局部性,后者是空間局部性。現(xiàn)代操作系統(tǒng)的設(shè)計,從硬件到操作系統(tǒng)再到應(yīng)用程序都利用了程序的局部性原理:硬件層,通過cache來緩存剛剛使用過的指令或者數(shù)據(jù),來提交對內(nèi)存的訪問效率。在操作系統(tǒng)級別,操作系統(tǒng)利用主存來緩存剛剛訪問過的磁盤塊;在應(yīng)用層,web瀏覽器將最近引用過的文檔放在磁盤上,大量的web服務(wù)器將最近訪問的文檔放在前端磁盤上,這些緩存能夠滿足很多請求而不需要服務(wù)器的干預(yù)。本文主要將的是硬件層次的程序局部性。
2.處理器存儲體系
計算機體系的儲存層次從內(nèi)到外依次是寄存器、cache(從一級、二級到三級)、主存、磁盤、遠程文件系統(tǒng);從內(nèi)到外,訪問速度依次降低,存儲容量依次增大。這個層次關(guān)系,可以用下面這張圖來表示:
程序在執(zhí)行過程中,數(shù)據(jù)最先在磁盤上,然后被取到內(nèi)存之中,最后如果經(jīng)過cache(也可以不經(jīng)過cache)被CPU使用。如果數(shù)據(jù)不再cache之中,需要CPU到主存中存取數(shù)據(jù),那么這就是cache miss,這將帶來相當大的時間開銷。
3.perf原理與使用簡介
Perf是Linux kernel自帶的系統(tǒng)性能優(yōu)化工具。Perf的優(yōu)勢在于與Linux Kernel的緊密結(jié)合,它可以最先應(yīng)用到加入Kernel的new feature。perf可以用于查看熱點函數(shù),查看cashe miss的比率,從而幫助開發(fā)者來優(yōu)化程序性能。
性能調(diào)優(yōu)工具如 perf,Oprofile 等的基本原理都是對被監(jiān)測對象進行采樣,最簡單的情形是根據(jù) tick 中斷進行采樣,即在 tick 中斷內(nèi)觸發(fā)采樣點,在采樣點里判斷程序當時的上下文。假如一個程序 90% 的時間都花費在函數(shù) foo() 上,那么 90% 的采樣點都應(yīng)該落在函數(shù) foo() 的上下文中。運氣不可捉摸,但我想只要采樣頻率足夠高,采樣時間足夠長,那么以上推論就比較可靠。因此,通過 tick 觸發(fā)采樣,我們便可以了解程序中哪些地方最耗時間,從而重點分析。
本文,我們主要關(guān)心的是cache miss事件,那么我們只需要統(tǒng)計程序cache miss的次數(shù)即可。使用perf 來檢測程序執(zhí)行期間由此造成的cache miss的命令是perf stat -e cache-misses ./exefilename,另外,檢測cache miss事件需要取消內(nèi)核指針的禁用(/proc/sys/kernel/kptr_restrict設(shè)置為0)。
4.cache 優(yōu)化實例
4.1數(shù)據(jù)合并
有兩個數(shù)據(jù)A和B,訪問的時候經(jīng)常是一起訪問的,總是會先訪問A再訪問B。這樣A[i]和B[i]就距離很遠,如果A、B是兩個長度很大的數(shù)組,那么可能A[i]和B[i]無法同時存在cache之中。為了增加程序訪問的局部性,需要將A[i]和B[i]盡量存放在一起。為此,我們可以定義一個結(jié)構(gòu)體,包含A和B的元素各一個。這樣的兩個程序?qū)Ρ热缦?#xff1a;
test.c
#define NUM 393216 int main(){ float a[NUM],b[NUM]; int i; for(i=0;i<1000;i++) add(a,b,NUM); } int add(int *a,int *b,int num){ int i=0; for(i=0;i<num;i++){ *a=*a+*b; a++; b++; } }test2.c
#define NUM 39216 typedef struct{ float a; float b; }Array; int main(){ Array myarray[NUM]; int j=0; for(j=0;j<1000;j++) add(myarray,NUM); } int add(Array *myarray,int num){ int i=0; for(i=0;i<num;i++){ myarray->a=myarray->a+myarray->b; myarray++; } }注:我們設(shè)置數(shù)組大小NUM為39216,因為cache大小是3072KB,這樣設(shè)定可以讓A數(shù)組填滿cache,方便對比。 對比二者的cache miss數(shù)量:
test
[huangyk@huangyk test]$ perf stat -e cache-misses ./test Performance counter stats for './test': 530,787 cache-misses 2.372003220 seconds time elapsedtest2
[huangyk@huangyk test]$ perf stat -e cache-misses ./test2 Performance counter stats for './test2': 11,636 cache-misses 0.233570690 seconds time elapsed可以看到,后者的cache miss數(shù)量相對前者有很大的下降,耗費的時間大概是前者的十分之一左右。
進一步,可以查看觸發(fā)cach-miss的函數(shù):
test程序的結(jié)果:
[huangyk@huangyk test]$ perf record -e cache-misses ./test [huangyk@huangyk test]$ perf report Samples: 7K of event 'cache-misses', Event count (approx.): 3393820 45.88% test test [.] sub 44.74% test test [.] add 0.71% test [kernel.kallsyms] [k] clear_page_c 0.70% test [kernel.kallsyms] [k] _spin_lock 0.43% test [kernel.kallsyms] [k] run_timer_softirq 0.41% test [kernel.kallsyms] [k] account_user_time 0.34% test [kernel.kallsyms] [k] hrtimer_interrupt 0.30% test [kernel.kallsyms] [k] run_posix_cpu_timers 0.29% test [kernel.kallsyms] [k] _cond_resched 0.24% test [kernel.kallsyms] [k] update_curr 0.23% test [kernel.kallsyms] [k] x86_pmu_disable 0.22% test [kernel.kallsyms] [k] __rcu_pending 0.20% test [kernel.kallsyms] [k] task_tick_fair 0.19% test [kernel.kallsyms] [k] account_process_tick 0.17% test [kernel.kallsyms] [k] scheduler_tick 0.16% test [kernel.kallsyms] [k] __perf_event_task_sched_out從perf輸出的結(jié)果可以看出,程序cache miss主要是由sub和add觸發(fā)的。
test2程序的結(jié)果:
perf record -e cache-misses ./test2 perf report Samples: 51 of event 'cache-misses', Event count (approx.): 17438 39.78% test2 [kernel.kallsyms] [k] clear_page_c 15.68% test2 [kernel.kallsyms] [k] mem_cgroup_uncharge_start 7.94% test2 [kernel.kallsyms] [k] __alloc_pages_nodemask 7.28% test2 [kernel.kallsyms] [k] init_fpu 6.50% test2 test2 [.] add 3.19% test2 [kernel.kallsyms] [k] arp_process 2.76% test2 [kernel.kallsyms] [k] account_user_time 2.73% test2 [kernel.kallsyms] [k] perf_event_mmap 2.02% test2 [kernel.kallsyms] [k] filemap_fault 1.62% test2 [kernel.kallsyms] [k] kfree 1.50% test2 [kernel.kallsyms] [k] 0xffffffffa04334b8 1.06% test2 [kernel.kallsyms] [k] _spin_lock 1.06% test2 [kernel.kallsyms] [k] raise_softirq 1.00% test2 [kernel.kallsyms] [k] acct_update_integrals 0.96% test2 [kernel.kallsyms] [k] __do_softirq 0.67% test2 [kernel.kallsyms] [k] handle_edge_irq 0.56% test2 [kernel.kallsyms] [k] __rcu_pending 0.40% test2 [kernel.kallsyms] [k] enqueue_hrtimer 0.39% test2 test2 [.] sub 0.38% test2 [kernel.kallsyms] [k] _spin_lock_irq 0.36% test2 [kernel.kallsyms] [k] tick_sched_timer從perf輸出的結(jié)果可以看出,add和sub觸發(fā)的perf miss已經(jīng)占了很小的一部分。
4.2循環(huán)交換
C語言中,對于二維數(shù)組,同一行的數(shù)據(jù)是相鄰的,同一列的數(shù)據(jù)是不相鄰的。如果在循環(huán)中,讓依次訪問的數(shù)據(jù)盡量處在內(nèi)存中相鄰的位置,那么程序的局部性將會得到很大的提高。 觀察下面矩陣相乘的幾個函數(shù):
test_ijk:
void Muti( double A[][NUM],double B[][NUM],double C[][NUM],int n){ int i,j,k; double sum=0; for (i=0;i<n;i++) for(j=0;j<n;j++){ sum=0.0; for(k=0;k<n;k++) sum+=A[i][k]*B[k][j]; C[i][j]+=sum; }test_jki:
void Muti( double A[][NUM],double B[][NUM],double C[][NUM],int n){ int i,j,k; double sum=0; for (j=0;j<n;j++) for(k=0;k<n;k++){ sum=B[k][j]; for(i=0;i<n;i++) C[i][j]+=A[i][k]*sum; } }test_kij:
void Muti( double A[][NUM],double B[][NUM],double C[][NUM],int n){ int i,j,k; double sum=0; for (k=0;k<n;k++) for(i=0;i<n;i++){ sum=A[i][k]; for(j=0;j<n;j++) C[i][j]+=B[k][j]*sum; } }考察內(nèi)層循環(huán),可以發(fā)現(xiàn),不同的循環(huán)模式,導致的cache失效比例依次是kij、ijk、jki遞增。
4.3循環(huán)合并
在很多情況下,我們可能使用兩個獨立的循環(huán)來訪問數(shù)組a和c。由于數(shù)組很大,在第二個循環(huán)訪問數(shù)組中元素的時候,第一個循環(huán)取進cache中的數(shù)據(jù)已經(jīng)被替換出去,從而導致cache失效。如此情況下,可以將兩個循環(huán)合并在一起。合并以后,每個數(shù)組元組在同一個循環(huán)體中被訪問了兩次,從而提高了程序的局部性。
5.后記
實際情況下,一個程序的cache失效比例往往并不像我們從理論上預(yù)測的那么簡單。影響cache失效比例的因素主要有:數(shù)組大小,cache映射策略,二級cache大小,Victim Cache等,同時由于cache的不同寫回策略,我們也很難從理論上預(yù)估一個程序由于cache miss而導致的時間耗費。真正在進行程序設(shè)計的時候,我們在進行理論上的分析之后,只有使用perf等性能調(diào)優(yōu)工具,才能更真實地觀察到程序?qū)ache的利用情況。
原文地址:https://www.ibm.com/developerworks/community/blogs/5144904d-5d75-45ed-9d2b-cf1754ee936a/entry/perf_introduction?lang=en
?
總結(jié)
以上是生活随笔為你收集整理的perf 性能分析实例——使用perf优化cache利用率的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 第9章 数据库完整性
- 下一篇: 茶叶类别及主要品目