解鎖程序性能密碼:CPU優(yōu)化全攻略
在數(shù)字世界里,CPU(Central Processing Unit,中央處理器)無(wú)疑是計(jì)算機(jī)的 “大腦”,是程序運(yùn)行的核心驅(qū)動(dòng)力。它就像一位不知疲倦的指揮官,高效地處理著各種復(fù)雜指令,決定著程序的運(yùn)行速度和響應(yīng)時(shí)間。無(wú)論是日常辦公軟件的流暢運(yùn)行,還是大型游戲、專業(yè)設(shè)計(jì)軟件的高性能表現(xiàn),CPU性能都起著決定性作用。當(dāng)CPU性能強(qiáng)勁時(shí),程序就像一輛在高速公路上疾馳的汽車,能夠快速響應(yīng)各種指令,實(shí)現(xiàn)絲滑的操作體驗(yàn);而當(dāng)CPU性能不足時(shí),程序則會(huì)像陷入泥沼的車輛,運(yùn)行緩慢,甚至出現(xiàn)卡頓和無(wú)響應(yīng)的情況 ,極大地影響用戶體驗(yàn)。
對(duì)于開發(fā)者而言,程序的CPU性能優(yōu)化是一場(chǎng)永無(wú)止境的探索。在硬件條件相對(duì)固定的情況下,通過(guò)巧妙的優(yōu)化手段提升程序?qū)PU資源的利用效率,就如同在有限的空間里精心布局,實(shí)現(xiàn)空間的最大化利用,讓程序在有限的硬件資源下發(fā)揮出最大的效能,這不僅是技術(shù)實(shí)力的體現(xiàn),更是滿足用戶日益增長(zhǎng)的性能需求、提升產(chǎn)品競(jìng)爭(zhēng)力的關(guān)鍵。
一、深入剖析CPU性能指標(biāo)
在程序的CPU性能優(yōu)化之旅中,了解關(guān)鍵的CPU性能指標(biāo)是至關(guān)重要的一步。這些指標(biāo)如同精密儀器上的刻度,精準(zhǔn)地反映出CPU的工作狀態(tài)和程序?qū)ζ滟Y源的利用效率,為我們的優(yōu)化工作提供了清晰的方向和有力的依據(jù) 。
1.1CPU 使用率:系統(tǒng)狀態(tài)的晴雨表
CPU 使用率是衡量 CPU 忙碌程度的關(guān)鍵指標(biāo),它直觀地反映了在某一時(shí)間段內(nèi),CPU 被程序占用的時(shí)間比例 。例如,當(dāng)我們?cè)陔娔X上同時(shí)運(yùn)行多個(gè)大型軟件時(shí),CPU 使用率會(huì)迅速上升,這表明 CPU 正全力以赴地處理各種任務(wù)。一般來(lái)說(shuō),當(dāng) CPU 使用率長(zhǎng)期處于 70%-90% 以上時(shí),就如同一個(gè)人長(zhǎng)時(shí)間高強(qiáng)度工作,可能會(huì)出現(xiàn)疲勞和效率下降的情況,此時(shí)系統(tǒng)很可能已經(jīng)遇到了性能瓶頸。比如,在一個(gè)在線游戲服務(wù)器中,如果 CPU 使用率持續(xù)過(guò)高,玩家可能會(huì)遇到游戲卡頓、延遲增加等問(wèn)題,嚴(yán)重影響游戲體驗(yàn)。
在 Linux 系統(tǒng)中,我們可以使用 top 命令實(shí)時(shí)查看系統(tǒng)中各個(gè)進(jìn)程的 CPU 使用率。在命令行中輸入 “top”,按下回車鍵后,會(huì)出現(xiàn)一個(gè)動(dòng)態(tài)更新的界面,其中 “% CPU” 列展示了每個(gè)進(jìn)程占用 CPU 的百分比。還可以使用 mpstat 命令,它來(lái)自 sysstat 包,能提供每個(gè) CPU 核心的詳細(xì)使用率信息。例如,“mpstat -P ALL 1” 表示每隔 1 秒輸出一次所有 CPU 核心的使用率情況,讓我們對(duì) CPU 的工作狀態(tài)有更細(xì)致的了解。
1.2用戶進(jìn)程與內(nèi)核進(jìn)程的CPU消耗
用戶進(jìn)程是我們?nèi)粘>帉懞瓦\(yùn)行的程序代碼,如各種業(yè)務(wù)邏輯、庫(kù)函數(shù)的調(diào)用等。當(dāng)我們運(yùn)行一個(gè)數(shù)據(jù)分析程序時(shí),數(shù)據(jù)的讀取、計(jì)算、處理等操作都屬于用戶進(jìn)程的范疇,這些操作會(huì)消耗一定的 CPU 資源。而內(nèi)核進(jìn)程則主要負(fù)責(zé)管理系統(tǒng)資源,如內(nèi)存的分配與回收、文件系統(tǒng)的操作、網(wǎng)絡(luò)通信的處理等。像內(nèi)存拷貝、系統(tǒng)調(diào)用等操作都屬于內(nèi)核進(jìn)程的工作,它們同樣會(huì)占用 CPU 時(shí)間。
當(dāng) CPU 使用率過(guò)高時(shí),我們需要準(zhǔn)確判斷是用戶進(jìn)程還是內(nèi)核進(jìn)程在 “作祟”。此時(shí),pidstat 工具就派上了用場(chǎng)。通過(guò) “pidstat -p < 進(jìn)程 ID>” 命令,我們可以查看指定進(jìn)程在用戶態(tài)(% usr)和內(nèi)核態(tài)(% system)消耗 CPU 的情況。如果 % usr 值較高,說(shuō)明用戶代碼中的某些操作,如大量的循環(huán)計(jì)算、復(fù)雜的算法執(zhí)行等,可能是導(dǎo)致 CPU 使用率升高的原因;如果 % system 值較高,則可能是內(nèi)核態(tài)的系統(tǒng)調(diào)用過(guò)于頻繁,或者存在大量的內(nèi)存拷貝等操作。此外,perf top 也是一個(gè)強(qiáng)大的工具,它可以實(shí)時(shí)顯示系統(tǒng)中 CPU 使用率最高的函數(shù),幫助我們快速定位到具體的問(wèn)題代碼,無(wú)論是用戶進(jìn)程還是內(nèi)核進(jìn)程中的問(wèn)題,都能一目了然。
1.3平均負(fù)載與上下文切換
平均負(fù)載是一個(gè)反映系統(tǒng)整體負(fù)載情況的重要指標(biāo),它表示單位時(shí)間內(nèi),系統(tǒng)處于可運(yùn)行狀態(tài)(正在使用 CPU 或者正在等待 CPU 調(diào)度)和不可中斷狀態(tài)(通常是等待硬件設(shè)備的 I/O 響應(yīng),如磁盤讀寫)的平均進(jìn)程數(shù)。簡(jiǎn)單來(lái)說(shuō),平均負(fù)載就像是一個(gè)繁忙的火車站候車大廳,里面的乘客就像系統(tǒng)中的進(jìn)程,平均負(fù)載反映了候車大廳里正在候車和即將上車的乘客數(shù)量。如果平均負(fù)載過(guò)高,就意味著候車大廳人滿為患,進(jìn)程需要等待很長(zhǎng)時(shí)間才能獲得 CPU 資源,從而導(dǎo)致系統(tǒng)運(yùn)行緩慢。
在 Linux 系統(tǒng)中,我們可以使用 uptime 命令查看平均負(fù)載,命令輸出的最后三個(gè)數(shù)字分別表示過(guò)去 1 分鐘、5 分鐘和 15 分鐘的平均負(fù)載值。例如,“12:34:56 up 1 day, 2:30, 3 users, load average: 0.50, 0.40, 0.30”,這里的 “0.50, 0.40, 0.30” 就是平均負(fù)載值。一般認(rèn)為,當(dāng)平均負(fù)載接近或超過(guò) CPU 核心數(shù)時(shí),系統(tǒng)可能會(huì)出現(xiàn)性能問(wèn)題,就像一個(gè)只能容納 100 人的候車大廳,卻來(lái)了 200 人,必然會(huì)導(dǎo)致?lián)頂D和混亂。
上下文切換則是指當(dāng) CPU 從一個(gè)進(jìn)程或線程切換到另一個(gè)進(jìn)程或線程時(shí),需要保存當(dāng)前任務(wù)的執(zhí)行狀態(tài)(如寄存器的值、程序計(jì)數(shù)器等),并加載下一個(gè)任務(wù)的執(zhí)行狀態(tài),這個(gè)過(guò)程就像一位演員在舞臺(tái)上表演完一個(gè)節(jié)目后,需要迅速換裝、準(zhǔn)備道具,然后再上臺(tái)表演下一個(gè)節(jié)目。上下文切換會(huì)導(dǎo)致 CPU 緩存被刷新,原本存儲(chǔ)在緩存中的數(shù)據(jù)需要從內(nèi)存中重新讀取,這會(huì)增加 CPU 的工作負(fù)擔(dān),影響系統(tǒng)性能。
我們可以使用 perf 和 vmstat 等工具來(lái)排查上下文切換的問(wèn)題。vmstat 命令中的 “cs” 字段表示每秒上下文切換的次數(shù),通過(guò)觀察這個(gè)值,我們可以了解系統(tǒng)上下文切換的頻繁程度。如果上下文切換次數(shù)過(guò)多,我們可以進(jìn)一步使用 perf 工具分析具體是哪些進(jìn)程或線程在頻繁地進(jìn)行上下文切換,從而針對(duì)性地進(jìn)行優(yōu)化,比如調(diào)整線程的調(diào)度策略、減少鎖的競(jìng)爭(zhēng)等,讓 CPU 能夠更高效地工作。
二、優(yōu)化策略大揭秘
2.1算法與數(shù)據(jù)結(jié)構(gòu)的優(yōu)化選擇
算法和數(shù)據(jù)結(jié)構(gòu)就像是程序的 “骨架”,其選擇的合理性直接關(guān)乎程序?qū)?CPU 資源的利用效率和執(zhí)行速度。以排序算法為例,冒泡排序的時(shí)間復(fù)雜度為 O (n2) ,在處理大量數(shù)據(jù)時(shí),就像一個(gè)人在堆滿雜物的房間里尋找物品,每一次比較都需要花費(fèi)大量時(shí)間,隨著數(shù)據(jù)量 n 的增大,其執(zhí)行時(shí)間會(huì)急劇增長(zhǎng),對(duì) CPU 資源的消耗也會(huì)變得極為可觀。而快速排序的平均時(shí)間復(fù)雜度為 O (n log n) ,它采用分治策略,如同將一個(gè)大問(wèn)題分解成多個(gè)小問(wèn)題逐一解決,大大提高了排序效率,能更高效地利用 CPU 資源,在處理大規(guī)模數(shù)據(jù)時(shí),明顯比冒泡排序要快得多。
在數(shù)據(jù)結(jié)構(gòu)方面,不同的存儲(chǔ)方式對(duì) CPU 性能也有著顯著影響。鏈表和數(shù)組是兩種常見的數(shù)據(jù)結(jié)構(gòu),鏈表在插入和刪除操作時(shí),就像在一串珠子中添加或移除一顆珠子,只需修改相鄰節(jié)點(diǎn)的指針,不需要移動(dòng)大量數(shù)據(jù),效率較高,對(duì) CPU 資源的占用相對(duì)較少。但在查找操作時(shí),鏈表需要從頭開始逐個(gè)遍歷節(jié)點(diǎn),就像在一條長(zhǎng)長(zhǎng)的隊(duì)伍中尋找某個(gè)人,時(shí)間復(fù)雜度較高,會(huì)消耗較多的 CPU 時(shí)間。而數(shù)組則相反,它在內(nèi)存中是連續(xù)存儲(chǔ)的,通過(guò)索引可以直接訪問(wèn)元素,查找操作就像在一個(gè)有明確座位號(hào)的電影院中找到自己的座位,速度非??欤艹浞掷?CPU 的快速訪問(wèn)能力。
但在插入和刪除操作時(shí),可能需要移動(dòng)大量元素,導(dǎo)致 CPU 進(jìn)行較多的數(shù)據(jù)搬運(yùn)工作,消耗更多資源。因此,在實(shí)際編程中,我們需要根據(jù)具體的業(yè)務(wù)需求和數(shù)據(jù)特點(diǎn),如數(shù)據(jù)的規(guī)模、操作的頻繁類型等,精心選擇合適的算法和數(shù)據(jù)結(jié)構(gòu),以實(shí)現(xiàn) CPU 性能的最大化利用 。
2.2編寫編譯器友好型代碼
(1)了解編譯器優(yōu)化選項(xiàng)
編譯器是將我們編寫的代碼轉(zhuǎn)換為可執(zhí)行程序的關(guān)鍵工具,它提供了豐富的優(yōu)化選項(xiàng),能幫助我們提升程序的 CPU 性能 。以常用的 GCC 編譯器為例,它提供了一系列從 -O0 到 -O3 的優(yōu)化級(jí)別,每個(gè)級(jí)別都有著不同的優(yōu)化側(cè)重點(diǎn)和效果 。
- -O0:這是不做任何優(yōu)化的級(jí)別,主要用于調(diào)試階段,能使調(diào)試產(chǎn)生預(yù)期的結(jié)果,因?yàn)樗A袅嗽即a的結(jié)構(gòu)和變量信息,方便我們追蹤代碼的執(zhí)行過(guò)程,但生成的可執(zhí)行文件在運(yùn)行時(shí)性能相對(duì)較低,就像一輛沒(méi)有經(jīng)過(guò)任何改裝的普通汽車,雖然穩(wěn)定但速度不快。
- -O1:對(duì)程序做部分編譯優(yōu)化,它會(huì)嘗試減小生成代碼的尺寸,以及縮短執(zhí)行時(shí)間,但并不執(zhí)行需要占用大量編譯時(shí)間的優(yōu)化。比如它會(huì)對(duì)代碼的分支、常量以及表達(dá)式等進(jìn)行優(yōu)化,像將一些簡(jiǎn)單的常量表達(dá)式在編譯時(shí)直接計(jì)算出結(jié)果,避免在運(yùn)行時(shí)重復(fù)計(jì)算,就像提前規(guī)劃好行程路線,避免不必要的繞路,從而提高了程序的運(yùn)行效率 。
- -O2:這是比 O1 更高級(jí)的優(yōu)化選項(xiàng),進(jìn)行了更多的優(yōu)化。GCC 將執(zhí)行幾乎所有的不包含時(shí)間和空間折中的優(yōu)化,例如執(zhí)行循環(huán)優(yōu)化,將常量表達(dá)式從循環(huán)中移除,簡(jiǎn)化判斷循環(huán)的條件,還會(huì)進(jìn)行全局公用子表達(dá)式消除等操作,進(jìn)一步減少了冗余計(jì)算,使程序運(yùn)行更加高效,就像給汽車換上了高性能的引擎和更輕量化的零部件,提升了整體性能 。
- -O3:在 - O2 的基礎(chǔ)上,進(jìn)一步執(zhí)行更激進(jìn)的優(yōu)化,如函數(shù)內(nèi)聯(lián)、循環(huán)展開等。函數(shù)內(nèi)聯(lián)會(huì)將一些短小的函數(shù)直接嵌入到調(diào)用處,避免了函數(shù)調(diào)用的開銷,就像將多個(gè)小工具合并成一個(gè)多功能工具,減少了使用時(shí)的切換成本;循環(huán)展開則是增加每次循環(huán)迭代計(jì)算的元素?cái)?shù)量,減少迭代次數(shù),提高了程序的并行性和執(zhí)行效率,如同多條生產(chǎn)線同時(shí)工作,加快了生產(chǎn)速度 。
除了這些常規(guī)的優(yōu)化級(jí)別,GCC 還提供了一些特殊選項(xiàng) :
- -Ofast:它在 - O3 的基礎(chǔ)上,進(jìn)一步放寬了一些數(shù)學(xué)計(jì)算的標(biāo)準(zhǔn),允許一些不符合 IEEE 754 標(biāo)準(zhǔn)的數(shù)學(xué)優(yōu)化,以換取更高的性能,適用于對(duì)計(jì)算精度要求不嚴(yán)格,但對(duì)性能要求極高的場(chǎng)景,比如一些圖形渲染、科學(xué)計(jì)算模擬等應(yīng)用,就像為了追求速度而選擇抄近路,雖然可能會(huì)有一些小風(fēng)險(xiǎn),但能大大提高效率 。
- -Og:主要用于優(yōu)化調(diào)試體驗(yàn),它在優(yōu)化代碼的同時(shí),盡可能地保持代碼的可調(diào)試性,生成的代碼既具有一定的優(yōu)化效果,又能讓調(diào)試過(guò)程更加直觀和方便,就像給汽車安裝了一個(gè)既不影響性能又能隨時(shí)查看車輛狀態(tài)的儀表盤 。
在實(shí)際使用中,我們需要根據(jù)項(xiàng)目的具體需求和場(chǎng)景,如是否處于開發(fā)調(diào)試階段、對(duì)程序執(zhí)行效率和代碼尺寸的要求等,合理選擇編譯器的優(yōu)化選項(xiàng),以達(dá)到最佳的性能和開發(fā)體驗(yàn)平衡 。
(2)避免編譯器優(yōu)化限制
在編寫代碼時(shí),有些因素可能會(huì)限制編譯器的優(yōu)化能力,從而影響程序的 CPU 性能 。內(nèi)存別名(memory aliasing)和副作用(side effect)就是兩個(gè)常見的問(wèn)題 。
內(nèi)存別名是指多個(gè)指針指向同一個(gè)內(nèi)存地址,這會(huì)讓編譯器在優(yōu)化時(shí)面臨困境。例如,有如下代碼:
void twiddle1(long* xp, long* yp) {
*xp += *yp;
*xp += *yp;
}
void twiddle2(long* xp, long* yp) {
*xp += 2 * *yp;
}
從邏輯上看,twiddle2 函數(shù)似乎比 twiddle1 函數(shù)更高效,編譯器可能會(huì)嘗試將 twiddle1 優(yōu)化成 twiddle2 的形式。但如果 xp 和 yp 指向同一個(gè)內(nèi)存地址,即出現(xiàn)了內(nèi)存別名,那么這兩個(gè)函數(shù)的行為就會(huì)不同,twiddle1 會(huì)將 xp 增加 4 倍,而 twiddle2 只會(huì)將 xp 增加 3 倍。為了避免這種情況對(duì)編譯器優(yōu)化的限制,我們可以使用 __restrict 修飾指針,它告訴編譯器,該指針?biāo)赶虻膬?nèi)存是唯一的,不會(huì)與其他指針產(chǎn)生別名,從而讓編譯器能夠放心地進(jìn)行優(yōu)化 。例如:
void twiddle3(long *__restrict xp, long *__restrict yp) {
*xp += *yp;
*xp += *yp;
}
副作用則是指函數(shù)除了返回值之外,還會(huì)對(duì)外部狀態(tài)產(chǎn)生影響,比如修改全局變量、進(jìn)行 I/O 操作等 。例如:
long counter = 0;
long f() {
return counter++;
}
long func1() {
return f() + f() + f() + f();
}
long func2() {
return 4 * f();
}
從表面上看,func1 和 func2 的結(jié)果應(yīng)該是相同的,但由于 f 函數(shù)存在副作用,會(huì)修改全局變量 counter,所以這兩個(gè)函數(shù)的實(shí)際行為和返回值是不同的 。大多數(shù)編譯器不會(huì)輕易判斷一個(gè)函數(shù)是否有副作用,為了保證程序的正確性,它們通常會(huì)假設(shè)函數(shù)存在副作用,從而限制了一些優(yōu)化策略 。
因此,在編寫代碼時(shí),我們應(yīng)盡量減少函數(shù)的副作用,或者明確告知編譯器函數(shù)沒(méi)有副作用,例如使用 __attribute__((pure)) 或 __attribute__((const)) 修飾函數(shù),前者表示函數(shù)除了返回值外不會(huì)對(duì)外部狀態(tài)產(chǎn)生影響,后者表示函數(shù)不僅沒(méi)有副作用,而且對(duì)于相同的輸入總是返回相同的結(jié)果,這樣可以幫助編譯器更好地進(jìn)行優(yōu)化 。
2.3基于硬件特性的深度優(yōu)化
(1)利用緩存機(jī)制
CPU緩存是位于CPU和內(nèi)存之間的高速存儲(chǔ)區(qū)域,由更快的SRAM構(gòu)成,其作用是存儲(chǔ) CPU 近期可能會(huì)頻繁訪問(wèn)的數(shù)據(jù)和指令 。當(dāng)CPU需要讀取數(shù)據(jù)時(shí),會(huì)首先在緩存中查找,如果找到(即緩存命中),就可以直接從緩存中讀取,這個(gè)過(guò)程只需要幾個(gè)時(shí)鐘周期,速度非常快;如果沒(méi)有找到(即緩存未命中),則需要從相對(duì)慢速的內(nèi)存中讀取,這可能需要上百個(gè)時(shí)鐘周期,會(huì)大大降低程序的執(zhí)行效率 。
CPU 緩存通常分為多級(jí),如一級(jí)緩存(L1 cache)、二級(jí)緩存(L2 cache)和三級(jí)緩存(L3 cache) 。每個(gè) CPU 核心都擁有自己獨(dú)立的一級(jí)緩存和二級(jí)緩存,而三級(jí)緩存則是多個(gè)核心共享的 。一級(jí)緩存又可細(xì)分為數(shù)據(jù)緩存(D - Cache)和指令緩存(I - Cache),分別用于存儲(chǔ)數(shù)據(jù)和指令,這樣可以同時(shí)被 CPU 訪問(wèn),減少了爭(zhēng)用 Cache 所造成的沖突,提高了 CPU 效能 。緩存的讀寫速度比內(nèi)存快很多,例如,對(duì)于 2GHz 主頻的 CPU 來(lái)說(shuō),訪問(wèn)一次內(nèi)存通常需要 100 個(gè)時(shí)鐘周期以上,而訪問(wèn)一級(jí)緩存只需要 4 - 5 個(gè)時(shí)鐘周期,二級(jí)緩存需要 12 個(gè)時(shí)鐘周期,三級(jí)緩存大約需要 30 個(gè)時(shí)鐘周期 。
為了提高緩存命中率,我們?cè)诰帉懘a時(shí)需要讓數(shù)據(jù)訪問(wèn)更符合緩存機(jī)制 。例如,在訪問(wèn)數(shù)組時(shí),按照內(nèi)存布局順序訪問(wèn)會(huì)帶來(lái)很大的性能提升 。假設(shè)有一個(gè)二維數(shù)組 arr[N][N],如果我們按照 arr[i][j] 的方式遍歷,即先遍歷行再遍歷列,這與數(shù)組在內(nèi)存中的存儲(chǔ)順序一致,當(dāng) CPU 訪問(wèn) arr[0][0] 時(shí),會(huì)將緊跟其后的 3 個(gè)元素(假設(shè)緩存行大小為 64 字節(jié),每個(gè)元素占用 4 字節(jié))也加載到緩存中,后續(xù)訪問(wèn) arr[0][1]、arr[0][2]、arr[0][3] 時(shí)就很可能命中緩存 。而如果按照 arr[j][i] 的方式遍歷,即先遍歷列再遍歷行,內(nèi)存是跳躍訪問(wèn)的,當(dāng) N 很大時(shí),很難將 arr[j + 1][i] 也讀入緩存,從而導(dǎo)致緩存命中率降低,程序執(zhí)行速度變慢 。
(2)向量化編程
向量化編程是一種利用 CPU 的 SIMD(Single Instruction, Multiple Data,單指令多數(shù)據(jù))指令集來(lái)同時(shí)處理多個(gè)數(shù)據(jù)的編程技術(shù) 。傳統(tǒng)的標(biāo)量編程每次只能處理一個(gè)數(shù)據(jù)元素,而向量化編程可以在一條指令中并行處理多個(gè)數(shù)據(jù)元素,大大提高了數(shù)據(jù)處理速度,減少了執(zhí)行時(shí)間 。例如,使用 SIMD 指令可以同時(shí)對(duì)多個(gè) 32 位浮點(diǎn)數(shù)或者 16 位整數(shù)進(jìn)行加法、乘法等運(yùn)算 。
以 NEON 指令集為例,它是 ARM 架構(gòu)中的一種 SIMD 擴(kuò)展,被廣泛應(yīng)用于移動(dòng)設(shè)備和嵌入式系統(tǒng)中 。在圖像、音頻處理等領(lǐng)域,NEON 指令集有著出色的表現(xiàn) 。在圖像濾波處理中,需要對(duì)圖像中的每個(gè)像素點(diǎn)進(jìn)行計(jì)算,傳統(tǒng)的方法是逐個(gè)像素點(diǎn)處理,效率較低 。而使用 NEON 指令集,可以將多個(gè)像素點(diǎn)的數(shù)據(jù)打包成一個(gè) 128 位的向量,然后通過(guò)一條指令對(duì)這些像素點(diǎn)同時(shí)進(jìn)行濾波計(jì)算,就像一群人同時(shí)完成多項(xiàng)任務(wù),大大提高了處理效率 。以下是一個(gè)簡(jiǎn)單的 NEON 指令集實(shí)現(xiàn)兩個(gè)浮點(diǎn)數(shù)組相加的示例代碼:
#include <arm_neon.h>
void vector_addition(float* A, float* B, float* C, int n) {
int i;
for (i = 0; i < n; i += 4) {
// 從A和B數(shù)組中加載4個(gè)浮點(diǎn)數(shù)到NEON寄存器
float32x4_t a = vld1q_f32(&A[i]);
float32x4_t b = vld1q_f32(&B[i]);
// 對(duì)兩個(gè)向量進(jìn)行加法運(yùn)算
float32x4_t c = vaddq_f32(a, b);
// 將結(jié)果存儲(chǔ)回C數(shù)組
vst1q_f32(&C[i], c);
}
}
在這個(gè)示例中,vld1q_f32 函數(shù)用于從內(nèi)存中加載 4 個(gè) 32 位浮點(diǎn)數(shù)到 NEON 寄存器,vaddq_f32 函數(shù)用于對(duì)兩個(gè)向量進(jìn)行加法運(yùn)算,vst1q_f32 函數(shù)用于將結(jié)果存儲(chǔ)回內(nèi)存 。通過(guò)這種方式,一次可以處理 4 個(gè)浮點(diǎn)數(shù),相比傳統(tǒng)的逐個(gè)處理方式,性能得到了顯著提升 。
三、實(shí)戰(zhàn)案例解析
3.1案例一:Java 進(jìn)程 CPU 飆升優(yōu)化
在實(shí)際開發(fā)中,我們經(jīng)常會(huì)遇到各種性能問(wèn)題,其中 Java 進(jìn)程 CPU 飆升是一個(gè)較為常見且棘手的問(wèn)題 。下面我們來(lái)看一個(gè)具體的案例 。
最近負(fù)責(zé)的一個(gè)項(xiàng)目上線后,運(yùn)行一段時(shí)間就發(fā)現(xiàn)對(duì)應(yīng)的進(jìn)程竟然占用了 700% 的 CPU,導(dǎo)致公司的物理服務(wù)器都不堪重負(fù),頻繁宕機(jī) 。面對(duì)這類 Java 進(jìn)程 CPU 飆升的問(wèn)題,我們?cè)撊绾味ㄎ唤鉀Q呢?
首先,采用 top 命令定位進(jìn)程 。登錄服務(wù)器,執(zhí)行 top 命令,查看 CPU 占用情況,很快就能發(fā)現(xiàn),PID 為 29706 的 Java 進(jìn)程的 CPU 飆升到 700% 多,且一直降不下來(lái),很顯然出現(xiàn)了問(wèn)題 。
接著,使用 top -Hp 命令定位線程 。使用 top -Hp 命令(其中為 Java 進(jìn)程的 id 號(hào))查看該 Java 進(jìn)程內(nèi)所有線程的資源占用情況(按 shft+p 按照 cpu 占用進(jìn)行排序,按 shift+m 按照內(nèi)存占用進(jìn)行排序) 。在這里,我們很容易發(fā)現(xiàn),多個(gè)線程的 CPU 占用達(dá)到了 90% 多 。我們挑選線程號(hào)為 30309 的線程繼續(xù)分析 。
然后,使用 jstack 命令定位代碼 。先將線程號(hào)轉(zhuǎn)換為 16 進(jìn)制,使用 printf “% x\n” 命令(tid 指線程的 id 號(hào))將 10 進(jìn)制的線程號(hào)轉(zhuǎn)換為 16 進(jìn)制 。轉(zhuǎn)換后的結(jié)果為 7665,由于導(dǎo)出的線程快照中線程的 nid 是 16 進(jìn)制的,而 16 進(jìn)制以 0x 開頭,所以對(duì)應(yīng)的 16 進(jìn)制的線程號(hào) nid 為 0x7665 。再采用 jstack 命令導(dǎo)出線程快照,通過(guò)使用 jdk 自帶命令 jstack 獲取該 java 進(jìn)程的線程快照并輸入到文件中 。最后,在生成的文件中根據(jù)線程號(hào) nid 搜索對(duì)應(yīng)的線程描述,判斷應(yīng)該是 ImageConverter.run () 方法中的代碼出現(xiàn)問(wèn)題 。
下面是 ImageConverter.run () 方法中的部分核心代碼 。這段代碼的邏輯是存儲(chǔ)minicap 的 socket 連接返回的數(shù)據(jù),設(shè)置阻塞隊(duì)列長(zhǎng)度,防止出現(xiàn)內(nèi)存溢出 。在 while 循環(huán)中,不斷讀取堵塞隊(duì)列dataQueue 中的數(shù)據(jù),如果數(shù)據(jù)為空,則執(zhí)行 continue 進(jìn)行下一次循環(huán) 。如果不為空,則通過(guò) poll () 方法讀取數(shù)據(jù),做相關(guān)邏輯處理 。初看這段代碼好像沒(méi)什么問(wèn)題,但是如果dataQueue對(duì)象長(zhǎng)期為空的話,這里就會(huì)一直空循環(huán),導(dǎo)致 CPU 飆升 。
// 全局變量
private BlockingQueue<byte[]> dataQueue = new LinkedBlockingQueue<byte[]>(100000);
// 消費(fèi)線程
@Override
public void run() {
// long start = System.currentTimeMillis();
while (isRunning) {
// 分析這里從LinkedBlockingQueue
if (dataQueue.isEmpty()) {
continue;
}
byte[] buffer = device.getMinicap().dataQueue.poll();
int len = buffer.length;
}
}
那么如何解決呢?分析 LinkedBlockingQueue 阻塞隊(duì)列的 API 發(fā)現(xiàn),有兩種取值的 API 。take () 方法取出隊(duì)列中的頭部元素,如果隊(duì)列為空則調(diào)用此方法的線程被阻塞等待,直到有元素能被取出,如果等待過(guò)程被中斷則拋出 InterruptedException;poll () 方法取出隊(duì)列中的頭部元素,如果隊(duì)列為空返回 null 。顯然 take 方法更適合這里的場(chǎng)景 。將代碼修改如下:
while (isRunning) {
/* if (device.getMinicap().dataQueue.isEmpty()) {
continue;
}*/
byte[] buffer = new byte[0];
try {
buffer = device.getMinicap().dataQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
……
}
重啟項(xiàng)目后,測(cè)試發(fā)現(xiàn)項(xiàng)目運(yùn)行穩(wěn)定,對(duì)應(yīng)項(xiàng)目進(jìn)程的 CPU 消耗占比不到 10% 。通過(guò)這個(gè)案例可以看出,在面對(duì) Java 進(jìn)程 CPU 飆升問(wèn)題時(shí),我們可以借助 top、jstack 等工具,逐步定位到問(wèn)題代碼,并通過(guò)合理的代碼修改來(lái)解決問(wèn)題 。
3.2案例二:UV 通道下采樣代碼優(yōu)化
在圖像和視頻處理等領(lǐng)域,常常會(huì)涉及到對(duì)圖像數(shù)據(jù)的各種操作,UV 通道下采樣就是其中常見的一種 。下面我們來(lái)看一個(gè) UV 通道下采樣代碼從標(biāo)量處理轉(zhuǎn)換為向量處理的優(yōu)化案例 。
假設(shè)我們有一個(gè) UV 通道下采樣的任務(wù),輸入是 u8 類型的數(shù)據(jù),通過(guò)鄰近的 4 個(gè)像素求平均,輸出 u8 類型的數(shù)據(jù),達(dá)到 1/4 下采樣的目的 。我們假定每行數(shù)據(jù)長(zhǎng)度是 16 的整數(shù)倍 。最初的 C 代碼實(shí)現(xiàn)如下:
void DownscaleUv(uint8_t *src, uint8_t *dst, int32_t src_stride, int32_t dst_width, int32_t dst_height, int32_t dst_stride) {
for (int32_t j = 0; j < dst_height; j++) {
uint8_t *src_ptr0 = src + src_stride * j * 2;
uint8_t *src_ptr1 = src_ptr0 + src_stride;
uint8_t *dst_ptr = dst + dst_stride * j;
for (int32_t i = 0; i < dst_width; i += 2) {
// U通道
dst_ptr[i] = (src_ptr0[i * 2] + src_ptr0[i * 2 + 2] +
src_ptr1[i * 2] + src_ptr1[i * 2 + 2]) / 4;
// V通道
dst_ptr[i + 1] = (src_ptr0[i * 2 + 1] + src_ptr0[i * 2 + 3] +
src_ptr1[i * 2 + 1] + src_ptr1[i * 2 + 3]) / 4;
}
}
}
為了提升代碼性能,我們可以將其轉(zhuǎn)換為向量處理,利用 NEON 指令集進(jìn)行優(yōu)化 。具體步驟如下:
①內(nèi)層循環(huán)向量化
內(nèi)層循環(huán)是代碼執(zhí)行次數(shù)最多的部分,因此是向量化的重點(diǎn) 。由于我們的輸入和輸出都是 u8 類型,NEON 寄存器 128bit,所以每次可以處理 16 個(gè)數(shù)據(jù) 。修改后的內(nèi)層循環(huán)代碼如下:
// 每次有16個(gè)數(shù)據(jù)輸出
for (i = 0; i < dst_width; i += 16) {
//數(shù)據(jù)處理部分......
}
②數(shù)據(jù)類型和指令選擇
輸入數(shù)據(jù)加載時(shí),UV 通道的數(shù)據(jù)是交織的,使用 vld2 指令可以實(shí)現(xiàn)解交織 。在數(shù)據(jù)處理過(guò)程中,選擇合適的指令進(jìn)行計(jì)算 。例如,水平兩個(gè)數(shù)據(jù)相加可以使用 vpaddlq_u8 指令,上下兩個(gè)數(shù)據(jù)相加之后求均值可以使用 vshrn_n_u16 和 vaddq_u16 指令 。
③代碼實(shí)現(xiàn)
#include <arm_neon.h>
void DownscaleUvNeon(uint8_t *src, uint8_t *dst, int32_t src_width, int32_t src_stride, int32_t dst_width, int32_t dst_height, int32_t dst_stride) {
//load偶數(shù)行的源數(shù)據(jù),2組每組16個(gè)u8類型數(shù)據(jù)
uint8x16x2_t v8_src0;
//load奇數(shù)行的源數(shù)據(jù),需要兩個(gè)Q寄存器
uint8x16x2_t v8_src1;
//目的數(shù)據(jù)變量,需要一個(gè)Q寄存器
uint8x8x2_t v8_dst;
//目前只處理16整數(shù)倍部分的結(jié)果
int32_t dst_width_align = dst_width & (-16);
//向量化剩余的部分需要單獨(dú)處理
int32_t remain = dst_width & 15;
int32_t i = 0;
//外層高度循環(huán),逐行處理
for (int32_t j = 0; j < dst_height; j++) {
//偶數(shù)行源數(shù)據(jù)指針
uint8_t *src_ptr0 = src + src_stride * j * 2;
//奇數(shù)行源數(shù)據(jù)指針
uint8_t *src_ptr1 = src_ptr0 + src_stride;
//目的數(shù)據(jù)指針
uint8_t *dst_ptr = dst + dst_stride * j;
//內(nèi)層循環(huán),一次16個(gè)u8結(jié)果輸出
for (i = 0; i < dst_width_align; i += 16) {
//提取數(shù)據(jù),進(jìn)行UV分離
v8_src0 = vld2q_u8(src_ptr0);
src_ptr0 += 32;
v8_src1 = vld2q_u8(src_ptr1);
src_ptr1 += 32;
//水平兩個(gè)數(shù)據(jù)相加
uint16x8_t v16_u_sum0 = vpaddlq_u8(v8_src0.val[0]);
uint16x8_t v16_v_sum0 = vpaddlq_u8(v8_src0.val[1]);
uint16x8_t v16_u_sum1 = vpaddlq_u8(v8_src1.val[0]);
uint16x8_t v16_v_sum1 = vpaddlq_u8(v8_src1.val[1]);
//上下兩個(gè)數(shù)據(jù)相加,之后求均值
v8_dst.val[0] = vshrn_n_u16(vaddq_u16(v16_u_sum0, v16_u_sum1), 2);
v8_dst.val[1] = vshrn_n_u16(vaddq_u16(v16_v_sum0, v16_v_sum1), 2);
//UV通道結(jié)果交織存儲(chǔ)
vst2_u8(dst_ptr, v8_dst);
dst_ptr += 16;
}
//process leftovers......
}
}
通過(guò)這樣的優(yōu)化,將原本的標(biāo)量處理轉(zhuǎn)換為向量處理,充分利用了 NEON 指令集的并行處理能力,大大提升了 UV 通道下采樣的效率 。在實(shí)際應(yīng)用中,對(duì)于圖像和視頻處理等對(duì)性能要求較高的場(chǎng)景,這種基于向量處理的優(yōu)化方式能夠顯著提高程序的運(yùn)行速度,為用戶帶來(lái)更好的體驗(yàn) 。
四、優(yōu)化工具大盤點(diǎn)
在程序 CPU 性能優(yōu)化的征程中,各類工具就像是我們的得力助手,它們能夠幫助我們精準(zhǔn)地監(jiān)測(cè)性能指標(biāo),深入分析代碼性能瓶頸,從而為優(yōu)化工作提供有力支持。下面,我們就來(lái)詳細(xì)盤點(diǎn)一些常用的優(yōu)化工具。
4.1性能監(jiān)控工具
(1)top
top 是 Linux 系統(tǒng)中一個(gè)非常強(qiáng)大的實(shí)時(shí)監(jiān)控系統(tǒng)性能的命令行工具 。它提供了關(guān)于系統(tǒng)正在運(yùn)行的進(jìn)程以及系統(tǒng)總體狀態(tài)的實(shí)時(shí)動(dòng)態(tài)視圖 。使用 top,我們可以看到關(guān)于 CPU 使用率、內(nèi)存使用情況、進(jìn)程信息、運(yùn)行時(shí)間、登錄用戶等關(guān)鍵數(shù)據(jù) 。在終端中輸入 top 并回車,即可啟動(dòng)該命令,顯示系統(tǒng)當(dāng)前的實(shí)時(shí)狀態(tài) 。其界面主要包括頂部區(qū)域,顯示系統(tǒng)的整體信息,如當(dāng)前時(shí)間、系統(tǒng)運(yùn)行時(shí)間、登錄用戶數(shù)、平均負(fù)載等;
任務(wù)(Tasks)和 CPU 狀態(tài)區(qū)域,展示當(dāng)前正在運(yùn)行的進(jìn)程數(shù)、休眠中的進(jìn)程數(shù)、停止的進(jìn)程數(shù)以及僵尸進(jìn)程數(shù),還有 CPU 的使用情況(用戶模式、系統(tǒng)模式、空閑等);內(nèi)存和交換空間區(qū)域,呈現(xiàn)物理內(nèi)存和交換空間的使用情況;進(jìn)程列表區(qū)域,列出當(dāng)前系統(tǒng)上所有進(jìn)程的詳細(xì)信息,通常包括 PID(進(jìn)程 ID)、用戶、優(yōu)先級(jí)、虛擬內(nèi)存使用量、物理內(nèi)存使用量、共享內(nèi)存大小、狀態(tài)(如運(yùn)行、睡眠等)、CPU 使用率、內(nèi)存使用率、運(yùn)行的時(shí)間以及命令行 。我們還可以使用 “-u 用戶名” 選項(xiàng)只顯示指定用戶的進(jìn)程;“-n 次數(shù)” 指定 top 命令更新屏幕的次數(shù),之后自動(dòng)退出;“-d 秒數(shù)” 設(shè)置 top 命令更新的時(shí)間間隔(以秒為單位)等 。
(2)htop
htop 是 top 的一個(gè)替代品,它提供了更加友好的交互界面,并且可以實(shí)時(shí)監(jiān)控系統(tǒng)的各項(xiàng)指標(biāo),包括 CPU 的使用情況 。htop 的界面更加直觀,將輸出界面劃分成了四個(gè)區(qū)域,上左區(qū)顯示了 CPU、物理內(nèi)存和交換分區(qū)的信息;上右區(qū)顯示了任務(wù)數(shù)量、平均負(fù)載和連接運(yùn)行時(shí)間等信息;進(jìn)程區(qū)域顯示出當(dāng)前系統(tǒng)中的所有進(jìn)程;操作提示區(qū)顯示了當(dāng)前界面中 F1 - F10 功能鍵中定義的快捷功能 。
例如,F(xiàn)1 用于顯示幫助信息;F2 可配置界面中的顯示信息,我們可以根據(jù)自己的需要修改顯式模式以及想要顯示的內(nèi)容,比如以 LED 的形式顯示 CPU 的使用情況,并且在左邊的區(qū)域添加 hostname,在右邊的區(qū)區(qū)域添加 clock,也可以自定義進(jìn)程區(qū)域中的顯示內(nèi)容;F3 用于進(jìn)程搜索;F4 是進(jìn)程過(guò)濾器;F5 顯示進(jìn)程樹;F6 用于排序;F7 減小 nice 值;F8 增加 nice 值;F9 殺掉指定進(jìn)程;F10 退出 htop 。
空格鍵用于標(biāo)記選中的進(jìn)程,用于實(shí)現(xiàn)對(duì)多個(gè)進(jìn)程同時(shí)操作;U 取消所有選中的進(jìn)程;s 顯示光標(biāo)所在進(jìn)程執(zhí)行的系統(tǒng)調(diào)用;l 顯示光標(biāo)所在進(jìn)程的文件列表;I 對(duì)排序的結(jié)果進(jìn)行反轉(zhuǎn)顯示 ;a 綁定進(jìn)程到指定的 CPU;u 顯示指定用戶的進(jìn)程;M 按照內(nèi)存使用百分比排序,對(duì)應(yīng) MEM% 列;P 按照 CPU 使用百分比排序,對(duì)應(yīng) CPU% 列;T 按照進(jìn)程運(yùn)行的時(shí)間排序,對(duì)應(yīng) TIME + 列 。
(3)mpstat
mpstat 是 Multiprocessor Statistics 的縮寫,是實(shí)時(shí)系統(tǒng)監(jiān)控工具 。其報(bào)告與 CPU 的一些統(tǒng)計(jì)信息,這些信息存放在 /proc/stat 文件中 。在多 CPUs 系統(tǒng)里,其不但能查看所有 CPU 的平均狀況信息,而且能夠查看特定 CPU 的信息 。mpstat 最大的特點(diǎn)是可以查看多核心 cpu 中每個(gè)計(jì)算核心的統(tǒng)計(jì)數(shù)據(jù),而類似工具 vmstat 只能查看系統(tǒng)整體 cpu 情況 。其語(yǔ)法為 “mpstat [-P {|ALL}] [internal [count]]” ,其中 “-P {|ALL}” 表示監(jiān)控哪個(gè) CPU, cpu 在 [0,cpu 個(gè)數(shù) - 1] 中取值;internal 是相鄰的兩次采樣的間隔時(shí)間;
count 是采樣的次數(shù),count 只能和 delay 一起使用 。當(dāng)沒(méi)有參數(shù)時(shí),mpstat 則顯示系統(tǒng)啟動(dòng)以后所有信息的平均值 。有 interval 時(shí),第一行的信息自系統(tǒng)啟動(dòng)以來(lái)的平均信息,從第二行開始,輸出為前一個(gè) interval 時(shí)間段的平均信息 。例如,“mpstat 2” 表示每 2 秒更新一次,顯示多核 CPU 核心的當(dāng)前運(yùn)行狀況信息;“mpstat -P ALL 2” 則可以查看每個(gè) cpu 核心的詳細(xì)當(dāng)前運(yùn)行狀況信息 。
(4)pidstat
pidstat 是一個(gè)常用的進(jìn)程性能分析工具,用來(lái)實(shí)時(shí)查看進(jìn)程的 CPU、內(nèi)存、I/O 以及上下文切換等性能指標(biāo) 。要查看所有進(jìn)程的 CPU 使用情況,使用 “pidstat” 命令,其輸出結(jié)果包括 PID(進(jìn)程 ID)、% usr(用戶態(tài) CPU 使用率)、% system(內(nèi)核態(tài) CPU 使用率)、% CPU(總的 CPU 使用率)等信息 。如果想在一段時(shí)間內(nèi)持續(xù)監(jiān)控進(jìn)程的 CPU 使用情況,可以使用 “pidstat 2 5” 這樣的命令格式,意味著每隔 2 秒刷新一次數(shù)據(jù),共顯示 5 次 。
若要查看指定進(jìn)程的 CPU 使用情況,假設(shè)進(jìn)程的 PID 為 1234,可使用 “pidstat -p 1234” 。pidstat 還能查看內(nèi)存使用情況,使用 “-r” 選項(xiàng),如 “pidstat -r”,將顯示 minflt/s(每秒次級(jí)頁(yè)面錯(cuò)誤數(shù))、majflt/s(每秒主頁(yè)面錯(cuò)誤數(shù))、VSZ(虛擬內(nèi)存大?。SS(駐留集大?。┑扰c內(nèi)存相關(guān)的信息 ,同樣也可以指定時(shí)間間隔和次數(shù)來(lái)持續(xù)監(jiān)控 。此外,使用 “-d” 選項(xiàng)可以監(jiān)控進(jìn)程的 I/O 操作,顯示 kB_rd/s(每秒從磁盤讀取的數(shù)據(jù)量)、kB_wr/s(每秒寫入磁盤的數(shù)據(jù)量)、kB_ccwr/s(取消寫入的千字節(jié)數(shù),由于緩存)等信息 ;使用 “-t” 選項(xiàng)可以顯示線程級(jí)別的監(jiān)控信息 。
4.2代碼分析工具
(1)perf
perf 是內(nèi)置于 Linux 內(nèi)核源碼樹中的性能剖析工具 。它基于事件采樣原理,使用了許多 Linux 跟蹤特性,可用于進(jìn)行函數(shù)級(jí)與指令級(jí)的性能瓶頸的查找與熱點(diǎn)代碼的定位 。
例如,“perf top” 可以實(shí)時(shí)顯示系統(tǒng) / 進(jìn)程的性能統(tǒng)計(jì)信息 ,常用參數(shù)包括 “-e” 指定性能事件,“-a” 顯示在所有 CPU 上的性能統(tǒng)計(jì)信息,“-C” 顯示在指定 CPU 上的性能統(tǒng)計(jì)信息,“-p” 指定進(jìn)程 PID,“-t” 指定線程 TID,“-K” 隱藏內(nèi)核統(tǒng)計(jì)信息,“-U”隱藏用戶空間的統(tǒng)計(jì)信息,“-s” 指定待解析的符號(hào)信息等 。
“perf stat” 用于分析系統(tǒng) / 進(jìn)程的整體性能概況 ,常用參數(shù)有 “-e” 選擇性能事件,“-i” 禁止子任務(wù)繼承父任務(wù)的性能計(jì)數(shù)器,“-r” 重復(fù)執(zhí)行 n 次目標(biāo)程序,并給出性能指標(biāo)在 n 次執(zhí)行中的變化范圍,“-n” 僅輸出目標(biāo)程序的執(zhí)行時(shí)間,而不開啟任何性能計(jì)數(shù)器,“-a” 指定全部 cpu,“-C” 指定某個(gè) cpu,“-A” 將給出每個(gè)處理器上相應(yīng)的信息,“-p” 指定待分析的進(jìn)程 id,“-t” 指定待分析的線程 id 等 。
“perf record” 用于記錄一段時(shí)間內(nèi)系統(tǒng) / 進(jìn)程的性能時(shí)間 ,常用參數(shù)包括 “-e” 選擇性能事件,“-p” 待分析進(jìn)程的 id,“-t” 待分析線程的 id,“-a” 分析整個(gè)系統(tǒng)的性能,“-C” 只采集指定 CPU 數(shù)據(jù),“-c” 事件的采樣周期,“-o” 指定輸出文件,默認(rèn)為 perf.data,“-A” 以 append 的方式寫輸出文件,“-f” 以 OverWrite 的方式寫輸出文件,“-g” 記錄函數(shù)間的調(diào)用關(guān)系 。
“perf report” 則用于讀取 perf record 生成的數(shù)據(jù)文件,并顯示分析數(shù)據(jù) ,常用參數(shù)有 “-i” 輸入的數(shù)據(jù)文件,“-v” 顯示每個(gè)符號(hào)的地址,“-d” 只顯示指定 dos 的符號(hào),“-C” 只顯示指定 comm 的信息(Comm. 觸發(fā)事件的進(jìn)程名),“-S” 只考慮指定符號(hào),“-U” 只顯示已解析的符號(hào),“-g [type,min,order]” 顯示調(diào)用關(guān)系,具體等同于 perf top 命令中的 “-g”,“-c” 只顯示指定 cpu 采樣信息 。
(2)gprof
gprof 是 GNU 提供的一款性能分析工具,它可以幫助我們分析程序的性能瓶頸 。使用 gprof,我們需要在編譯程序時(shí)加上 “-pg” 選項(xiàng),例如 “gcc -pg -o myprogram myprogram.c” 。編譯完成后運(yùn)行程序,程序運(yùn)行結(jié)束后會(huì)生成一個(gè)名為 “gmon.out” 的文件 。然后使用 “gprof” 命令加上可執(zhí)行文件名和 “gmon.out” 文件來(lái)進(jìn)行分析,如 “gprof myprogram gmon.out” 。
gprof 會(huì)生成一份詳細(xì)的報(bào)告,展示函數(shù)的調(diào)用關(guān)系、每個(gè)函數(shù)的執(zhí)行時(shí)間、調(diào)用次數(shù)等信息 。通過(guò)這份報(bào)告,我們可以清楚地看到哪些函數(shù)占用了較多的執(zhí)行時(shí)間,從而有針對(duì)性地對(duì)這些函數(shù)進(jìn)行優(yōu)化 。例如,如果報(bào)告顯示某個(gè)函數(shù)的執(zhí)行時(shí)間很長(zhǎng),且被頻繁調(diào)用,那么我們就可以深入分析該函數(shù)的代碼邏輯,嘗試優(yōu)化算法或者減少不必要的操作,以提高程序的整體性能 。
(3)valgrind
valgrind 是一套功能強(qiáng)大的調(diào)試和分析工具,其中的 Massif 工具可以用來(lái)分析程序的內(nèi)存使用情況,Cachegrind 工具則可以用于分析 CPU 緩存的使用情況 。使用 Massif 分析內(nèi)存時(shí),運(yùn)行程序時(shí)使用 “valgrind --tool=massif myprogram” 命令 ,程序運(yùn)行結(jié)束后會(huì)生成一個(gè)名為 “massif.out.XXXX”(XXXX 為數(shù)字)的文件 。然后可以使用 “ms_print massif.out.XXXX” 命令來(lái)查看內(nèi)存使用報(bào)告,報(bào)告中會(huì)顯示程序在不同時(shí)間點(diǎn)的堆內(nèi)存使用量、峰值內(nèi)存使用量等信息 ,幫助我們發(fā)現(xiàn)內(nèi)存泄漏、內(nèi)存分配不合理等問(wèn)題 。
使用 Cachegrind 分析 CPU 緩存時(shí),運(yùn)行程序時(shí)使用 “valgrind --tool=cachegrind myprogram” 命令 ,運(yùn)行結(jié)束后會(huì)生成 “cachegrind.out.XXXX” 文件 ,通過(guò) “cg_annotate cachegrind.out.XXXX” 命令可以查看緩存使用報(bào)告,報(bào)告中會(huì)展示函數(shù)的緩存命中率、緩存缺失次數(shù)等信息 ,讓我們了解程序?qū)?CPU 緩存的利用情況,進(jìn)而通過(guò)優(yōu)化數(shù)據(jù)訪問(wèn)模式、調(diào)整代碼結(jié)構(gòu)等方式提高緩存命中率,提升程序性能 。