自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

解鎖程序性能密碼:CPU優(yōu)化全攻略

開發(fā) 開發(fā)工具
valgrind 是一套功能強大的調(diào)試和分析工具,其中的 Massif 工具可以用來分析程序的內(nèi)存使用情況,Cachegrind 工具則可以用于分析 CPU 緩存的使用情況 。

在數(shù)字世界里,CPU(Central Processing Unit,中央處理器)無疑是計算機的 “大腦”,是程序運行的核心驅(qū)動力。它就像一位不知疲倦的指揮官,高效地處理著各種復(fù)雜指令,決定著程序的運行速度和響應(yīng)時間。無論是日常辦公軟件的流暢運行,還是大型游戲、專業(yè)設(shè)計軟件的高性能表現(xiàn),CPU性能都起著決定性作用。當CPU性能強勁時,程序就像一輛在高速公路上疾馳的汽車,能夠快速響應(yīng)各種指令,實現(xiàn)絲滑的操作體驗;而當CPU性能不足時,程序則會像陷入泥沼的車輛,運行緩慢,甚至出現(xiàn)卡頓和無響應(yīng)的情況 ,極大地影響用戶體驗。

對于開發(fā)者而言,程序的CPU性能優(yōu)化是一場永無止境的探索。在硬件條件相對固定的情況下,通過巧妙的優(yōu)化手段提升程序?qū)PU資源的利用效率,就如同在有限的空間里精心布局,實現(xiàn)空間的最大化利用,讓程序在有限的硬件資源下發(fā)揮出最大的效能,這不僅是技術(shù)實力的體現(xiàn),更是滿足用戶日益增長的性能需求、提升產(chǎn)品競爭力的關(guān)鍵。

一、深入剖析CPU性能指標

在程序的CPU性能優(yōu)化之旅中,了解關(guān)鍵的CPU性能指標是至關(guān)重要的一步。這些指標如同精密儀器上的刻度,精準地反映出CPU的工作狀態(tài)和程序?qū)ζ滟Y源的利用效率,為我們的優(yōu)化工作提供了清晰的方向和有力的依據(jù) 。

1.1CPU 使用率:系統(tǒng)狀態(tài)的晴雨表

CPU 使用率是衡量 CPU 忙碌程度的關(guān)鍵指標,它直觀地反映了在某一時間段內(nèi),CPU 被程序占用的時間比例 。例如,當我們在電腦上同時運行多個大型軟件時,CPU 使用率會迅速上升,這表明 CPU 正全力以赴地處理各種任務(wù)。一般來說,當 CPU 使用率長期處于 70%-90% 以上時,就如同一個人長時間高強度工作,可能會出現(xiàn)疲勞和效率下降的情況,此時系統(tǒng)很可能已經(jīng)遇到了性能瓶頸。比如,在一個在線游戲服務(wù)器中,如果 CPU 使用率持續(xù)過高,玩家可能會遇到游戲卡頓、延遲增加等問題,嚴重影響游戲體驗。

在 Linux 系統(tǒng)中,我們可以使用 top 命令實時查看系統(tǒng)中各個進程的 CPU 使用率。在命令行中輸入 “top”,按下回車鍵后,會出現(xiàn)一個動態(tài)更新的界面,其中 “% CPU” 列展示了每個進程占用 CPU 的百分比。還可以使用 mpstat 命令,它來自 sysstat 包,能提供每個 CPU 核心的詳細使用率信息。例如,“mpstat -P ALL 1” 表示每隔 1 秒輸出一次所有 CPU 核心的使用率情況,讓我們對 CPU 的工作狀態(tài)有更細致的了解。

1.2用戶進程與內(nèi)核進程的CPU消耗

用戶進程是我們?nèi)粘>帉懞瓦\行的程序代碼,如各種業(yè)務(wù)邏輯、庫函數(shù)的調(diào)用等。當我們運行一個數(shù)據(jù)分析程序時,數(shù)據(jù)的讀取、計算、處理等操作都屬于用戶進程的范疇,這些操作會消耗一定的 CPU 資源。而內(nèi)核進程則主要負責管理系統(tǒng)資源,如內(nèi)存的分配與回收、文件系統(tǒng)的操作、網(wǎng)絡(luò)通信的處理等。像內(nèi)存拷貝、系統(tǒng)調(diào)用等操作都屬于內(nèi)核進程的工作,它們同樣會占用 CPU 時間。

當 CPU 使用率過高時,我們需要準確判斷是用戶進程還是內(nèi)核進程在 “作祟”。此時,pidstat 工具就派上了用場。通過 “pidstat -p < 進程 ID>” 命令,我們可以查看指定進程在用戶態(tài)(% usr)和內(nèi)核態(tài)(% system)消耗 CPU 的情況。如果 % usr 值較高,說明用戶代碼中的某些操作,如大量的循環(huán)計算、復(fù)雜的算法執(zhí)行等,可能是導致 CPU 使用率升高的原因;如果 % system 值較高,則可能是內(nèi)核態(tài)的系統(tǒng)調(diào)用過于頻繁,或者存在大量的內(nèi)存拷貝等操作。此外,perf top 也是一個強大的工具,它可以實時顯示系統(tǒng)中 CPU 使用率最高的函數(shù),幫助我們快速定位到具體的問題代碼,無論是用戶進程還是內(nèi)核進程中的問題,都能一目了然。

1.3平均負載與上下文切換

平均負載是一個反映系統(tǒng)整體負載情況的重要指標,它表示單位時間內(nèi),系統(tǒng)處于可運行狀態(tài)(正在使用 CPU 或者正在等待 CPU 調(diào)度)和不可中斷狀態(tài)(通常是等待硬件設(shè)備的 I/O 響應(yīng),如磁盤讀寫)的平均進程數(shù)。簡單來說,平均負載就像是一個繁忙的火車站候車大廳,里面的乘客就像系統(tǒng)中的進程,平均負載反映了候車大廳里正在候車和即將上車的乘客數(shù)量。如果平均負載過高,就意味著候車大廳人滿為患,進程需要等待很長時間才能獲得 CPU 資源,從而導致系統(tǒng)運行緩慢。

在 Linux 系統(tǒng)中,我們可以使用 uptime 命令查看平均負載,命令輸出的最后三個數(shù)字分別表示過去 1 分鐘、5 分鐘和 15 分鐘的平均負載值。例如,“12:34:56 up 1 day, 2:30, 3 users, load average: 0.50, 0.40, 0.30”,這里的 “0.50, 0.40, 0.30” 就是平均負載值。一般認為,當平均負載接近或超過 CPU 核心數(shù)時,系統(tǒng)可能會出現(xiàn)性能問題,就像一個只能容納 100 人的候車大廳,卻來了 200 人,必然會導致?lián)頂D和混亂。

上下文切換則是指當 CPU 從一個進程或線程切換到另一個進程或線程時,需要保存當前任務(wù)的執(zhí)行狀態(tài)(如寄存器的值、程序計數(shù)器等),并加載下一個任務(wù)的執(zhí)行狀態(tài),這個過程就像一位演員在舞臺上表演完一個節(jié)目后,需要迅速換裝、準備道具,然后再上臺表演下一個節(jié)目。上下文切換會導致 CPU 緩存被刷新,原本存儲在緩存中的數(shù)據(jù)需要從內(nèi)存中重新讀取,這會增加 CPU 的工作負擔,影響系統(tǒng)性能。

我們可以使用 perf 和 vmstat 等工具來排查上下文切換的問題。vmstat 命令中的 “cs” 字段表示每秒上下文切換的次數(shù),通過觀察這個值,我們可以了解系統(tǒng)上下文切換的頻繁程度。如果上下文切換次數(shù)過多,我們可以進一步使用 perf 工具分析具體是哪些進程或線程在頻繁地進行上下文切換,從而針對性地進行優(yōu)化,比如調(diào)整線程的調(diào)度策略、減少鎖的競爭等,讓 CPU 能夠更高效地工作。

二、優(yōu)化策略大揭秘

2.1算法與數(shù)據(jù)結(jié)構(gòu)的優(yōu)化選擇

算法和數(shù)據(jù)結(jié)構(gòu)就像是程序的 “骨架”,其選擇的合理性直接關(guān)乎程序?qū)?CPU 資源的利用效率和執(zhí)行速度。以排序算法為例,冒泡排序的時間復(fù)雜度為 O (n2) ,在處理大量數(shù)據(jù)時,就像一個人在堆滿雜物的房間里尋找物品,每一次比較都需要花費大量時間,隨著數(shù)據(jù)量 n 的增大,其執(zhí)行時間會急劇增長,對 CPU 資源的消耗也會變得極為可觀。而快速排序的平均時間復(fù)雜度為 O (n log n) ,它采用分治策略,如同將一個大問題分解成多個小問題逐一解決,大大提高了排序效率,能更高效地利用 CPU 資源,在處理大規(guī)模數(shù)據(jù)時,明顯比冒泡排序要快得多。

在數(shù)據(jù)結(jié)構(gòu)方面,不同的存儲方式對 CPU 性能也有著顯著影響。鏈表和數(shù)組是兩種常見的數(shù)據(jù)結(jié)構(gòu),鏈表在插入和刪除操作時,就像在一串珠子中添加或移除一顆珠子,只需修改相鄰節(jié)點的指針,不需要移動大量數(shù)據(jù),效率較高,對 CPU 資源的占用相對較少。但在查找操作時,鏈表需要從頭開始逐個遍歷節(jié)點,就像在一條長長的隊伍中尋找某個人,時間復(fù)雜度較高,會消耗較多的 CPU 時間。而數(shù)組則相反,它在內(nèi)存中是連續(xù)存儲的,通過索引可以直接訪問元素,查找操作就像在一個有明確座位號的電影院中找到自己的座位,速度非???,能充分利用 CPU 的快速訪問能力。

但在插入和刪除操作時,可能需要移動大量元素,導致 CPU 進行較多的數(shù)據(jù)搬運工作,消耗更多資源。因此,在實際編程中,我們需要根據(jù)具體的業(yè)務(wù)需求和數(shù)據(jù)特點,如數(shù)據(jù)的規(guī)模、操作的頻繁類型等,精心選擇合適的算法和數(shù)據(jù)結(jié)構(gòu),以實現(xiàn) CPU 性能的最大化利用 。

2.2編寫編譯器友好型代碼

(1)了解編譯器優(yōu)化選項

編譯器是將我們編寫的代碼轉(zhuǎn)換為可執(zhí)行程序的關(guān)鍵工具,它提供了豐富的優(yōu)化選項,能幫助我們提升程序的 CPU 性能 。以常用的 GCC 編譯器為例,它提供了一系列從 -O0 到 -O3 的優(yōu)化級別,每個級別都有著不同的優(yōu)化側(cè)重點和效果 。

  • -O0:這是不做任何優(yōu)化的級別,主要用于調(diào)試階段,能使調(diào)試產(chǎn)生預(yù)期的結(jié)果,因為它保留了原始代碼的結(jié)構(gòu)和變量信息,方便我們追蹤代碼的執(zhí)行過程,但生成的可執(zhí)行文件在運行時性能相對較低,就像一輛沒有經(jīng)過任何改裝的普通汽車,雖然穩(wěn)定但速度不快。
  • -O1:對程序做部分編譯優(yōu)化,它會嘗試減小生成代碼的尺寸,以及縮短執(zhí)行時間,但并不執(zhí)行需要占用大量編譯時間的優(yōu)化。比如它會對代碼的分支、常量以及表達式等進行優(yōu)化,像將一些簡單的常量表達式在編譯時直接計算出結(jié)果,避免在運行時重復(fù)計算,就像提前規(guī)劃好行程路線,避免不必要的繞路,從而提高了程序的運行效率 。
  • -O2:這是比 O1 更高級的優(yōu)化選項,進行了更多的優(yōu)化。GCC 將執(zhí)行幾乎所有的不包含時間和空間折中的優(yōu)化,例如執(zhí)行循環(huán)優(yōu)化,將常量表達式從循環(huán)中移除,簡化判斷循環(huán)的條件,還會進行全局公用子表達式消除等操作,進一步減少了冗余計算,使程序運行更加高效,就像給汽車換上了高性能的引擎和更輕量化的零部件,提升了整體性能 。
  • -O3:在 - O2 的基礎(chǔ)上,進一步執(zhí)行更激進的優(yōu)化,如函數(shù)內(nèi)聯(lián)、循環(huán)展開等。函數(shù)內(nèi)聯(lián)會將一些短小的函數(shù)直接嵌入到調(diào)用處,避免了函數(shù)調(diào)用的開銷,就像將多個小工具合并成一個多功能工具,減少了使用時的切換成本;循環(huán)展開則是增加每次循環(huán)迭代計算的元素數(shù)量,減少迭代次數(shù),提高了程序的并行性和執(zhí)行效率,如同多條生產(chǎn)線同時工作,加快了生產(chǎn)速度 。

除了這些常規(guī)的優(yōu)化級別,GCC 還提供了一些特殊選項 :

  • -Ofast:它在 - O3 的基礎(chǔ)上,進一步放寬了一些數(shù)學計算的標準,允許一些不符合 IEEE 754 標準的數(shù)學優(yōu)化,以換取更高的性能,適用于對計算精度要求不嚴格,但對性能要求極高的場景,比如一些圖形渲染、科學計算模擬等應(yīng)用,就像為了追求速度而選擇抄近路,雖然可能會有一些小風險,但能大大提高效率 。
  • -Og:主要用于優(yōu)化調(diào)試體驗,它在優(yōu)化代碼的同時,盡可能地保持代碼的可調(diào)試性,生成的代碼既具有一定的優(yōu)化效果,又能讓調(diào)試過程更加直觀和方便,就像給汽車安裝了一個既不影響性能又能隨時查看車輛狀態(tài)的儀表盤 。

在實際使用中,我們需要根據(jù)項目的具體需求和場景,如是否處于開發(fā)調(diào)試階段、對程序執(zhí)行效率和代碼尺寸的要求等,合理選擇編譯器的優(yōu)化選項,以達到最佳的性能和開發(fā)體驗平衡 。

(2)避免編譯器優(yōu)化限制

在編寫代碼時,有些因素可能會限制編譯器的優(yōu)化能力,從而影響程序的 CPU 性能 。內(nèi)存別名(memory aliasing)和副作用(side effect)就是兩個常見的問題 。

內(nèi)存別名是指多個指針指向同一個內(nèi)存地址,這會讓編譯器在優(yōu)化時面臨困境。例如,有如下代碼:

void twiddle1(long* xp, long* yp) {
    *xp += *yp;
    *xp += *yp;
}
void twiddle2(long* xp, long* yp) {
    *xp += 2 * *yp;
}

從邏輯上看,twiddle2 函數(shù)似乎比 twiddle1 函數(shù)更高效,編譯器可能會嘗試將 twiddle1 優(yōu)化成 twiddle2 的形式。但如果 xp 和 yp 指向同一個內(nèi)存地址,即出現(xiàn)了內(nèi)存別名,那么這兩個函數(shù)的行為就會不同,twiddle1 會將 xp 增加 4 倍,而 twiddle2 只會將 xp 增加 3 倍。為了避免這種情況對編譯器優(yōu)化的限制,我們可以使用 __restrict 修飾指針,它告訴編譯器,該指針所指向的內(nèi)存是唯一的,不會與其他指針產(chǎn)生別名,從而讓編譯器能夠放心地進行優(yōu)化 。例如:

void twiddle3(long *__restrict xp, long *__restrict yp) {
    *xp += *yp;
    *xp += *yp;
}

副作用則是指函數(shù)除了返回值之外,還會對外部狀態(tài)產(chǎ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ù)存在副作用,會修改全局變量 counter,所以這兩個函數(shù)的實際行為和返回值是不同的 。大多數(shù)編譯器不會輕易判斷一個函數(shù)是否有副作用,為了保證程序的正確性,它們通常會假設(shè)函數(shù)存在副作用,從而限制了一些優(yōu)化策略 。

因此,在編寫代碼時,我們應(yīng)盡量減少函數(shù)的副作用,或者明確告知編譯器函數(shù)沒有副作用,例如使用 __attribute__((pure)) 或 __attribute__((const)) 修飾函數(shù),前者表示函數(shù)除了返回值外不會對外部狀態(tài)產(chǎn)生影響,后者表示函數(shù)不僅沒有副作用,而且對于相同的輸入總是返回相同的結(jié)果,這樣可以幫助編譯器更好地進行優(yōu)化 。

2.3基于硬件特性的深度優(yōu)化

(1)利用緩存機制

CPU緩存是位于CPU和內(nèi)存之間的高速存儲區(qū)域,由更快的SRAM構(gòu)成,其作用是存儲 CPU 近期可能會頻繁訪問的數(shù)據(jù)和指令 。當CPU需要讀取數(shù)據(jù)時,會首先在緩存中查找,如果找到(即緩存命中),就可以直接從緩存中讀取,這個過程只需要幾個時鐘周期,速度非???;如果沒有找到(即緩存未命中),則需要從相對慢速的內(nèi)存中讀取,這可能需要上百個時鐘周期,會大大降低程序的執(zhí)行效率 。

CPU 緩存通常分為多級,如一級緩存(L1 cache)、二級緩存(L2 cache)和三級緩存(L3 cache) 。每個 CPU 核心都擁有自己獨立的一級緩存和二級緩存,而三級緩存則是多個核心共享的 。一級緩存又可細分為數(shù)據(jù)緩存(D - Cache)和指令緩存(I - Cache),分別用于存儲數(shù)據(jù)和指令,這樣可以同時被 CPU 訪問,減少了爭用 Cache 所造成的沖突,提高了 CPU 效能 。緩存的讀寫速度比內(nèi)存快很多,例如,對于 2GHz 主頻的 CPU 來說,訪問一次內(nèi)存通常需要 100 個時鐘周期以上,而訪問一級緩存只需要 4 - 5 個時鐘周期,二級緩存需要 12 個時鐘周期,三級緩存大約需要 30 個時鐘周期 。

為了提高緩存命中率,我們在編寫代碼時需要讓數(shù)據(jù)訪問更符合緩存機制 。例如,在訪問數(shù)組時,按照內(nèi)存布局順序訪問會帶來很大的性能提升 。假設(shè)有一個二維數(shù)組 arr[N][N],如果我們按照 arr[i][j] 的方式遍歷,即先遍歷行再遍歷列,這與數(shù)組在內(nèi)存中的存儲順序一致,當 CPU 訪問 arr[0][0] 時,會將緊跟其后的 3 個元素(假設(shè)緩存行大小為 64 字節(jié),每個元素占用 4 字節(jié))也加載到緩存中,后續(xù)訪問 arr[0][1]、arr[0][2]、arr[0][3] 時就很可能命中緩存 。而如果按照 arr[j][i] 的方式遍歷,即先遍歷列再遍歷行,內(nèi)存是跳躍訪問的,當 N 很大時,很難將 arr[j + 1][i] 也讀入緩存,從而導致緩存命中率降低,程序執(zhí)行速度變慢 。

(2)向量化編程

向量化編程是一種利用 CPU 的 SIMD(Single Instruction, Multiple Data,單指令多數(shù)據(jù))指令集來同時處理多個數(shù)據(jù)的編程技術(shù) 。傳統(tǒng)的標量編程每次只能處理一個數(shù)據(jù)元素,而向量化編程可以在一條指令中并行處理多個數(shù)據(jù)元素,大大提高了數(shù)據(jù)處理速度,減少了執(zhí)行時間 。例如,使用 SIMD 指令可以同時對多個 32 位浮點數(shù)或者 16 位整數(shù)進行加法、乘法等運算 。

以 NEON 指令集為例,它是 ARM 架構(gòu)中的一種 SIMD 擴展,被廣泛應(yīng)用于移動設(shè)備和嵌入式系統(tǒng)中 。在圖像、音頻處理等領(lǐng)域,NEON 指令集有著出色的表現(xiàn) 。在圖像濾波處理中,需要對圖像中的每個像素點進行計算,傳統(tǒng)的方法是逐個像素點處理,效率較低 。而使用 NEON 指令集,可以將多個像素點的數(shù)據(jù)打包成一個 128 位的向量,然后通過一條指令對這些像素點同時進行濾波計算,就像一群人同時完成多項任務(wù),大大提高了處理效率 。以下是一個簡單的 NEON 指令集實現(xià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個浮點數(shù)到NEON寄存器
        float32x4_t a = vld1q_f32(&A[i]);
        float32x4_t b = vld1q_f32(&B[i]);
        // 對兩個向量進行加法運算
        float32x4_t c = vaddq_f32(a, b);
        // 將結(jié)果存儲回C數(shù)組
        vst1q_f32(&C[i], c);
    }
}

在這個示例中,vld1q_f32 函數(shù)用于從內(nèi)存中加載 4 個 32 位浮點數(shù)到 NEON 寄存器,vaddq_f32 函數(shù)用于對兩個向量進行加法運算,vst1q_f32 函數(shù)用于將結(jié)果存儲回內(nèi)存 。通過這種方式,一次可以處理 4 個浮點數(shù),相比傳統(tǒng)的逐個處理方式,性能得到了顯著提升 。

三、實戰(zhàn)案例解析

3.1案例一:Java 進程 CPU 飆升優(yōu)化

在實際開發(fā)中,我們經(jīng)常會遇到各種性能問題,其中 Java 進程 CPU 飆升是一個較為常見且棘手的問題 。下面我們來看一個具體的案例 。

最近負責的一個項目上線后,運行一段時間就發(fā)現(xiàn)對應(yīng)的進程竟然占用了 700% 的 CPU,導致公司的物理服務(wù)器都不堪重負,頻繁宕機 。面對這類 Java 進程 CPU 飆升的問題,我們該如何定位解決呢?

首先,采用 top 命令定位進程 。登錄服務(wù)器,執(zhí)行 top 命令,查看 CPU 占用情況,很快就能發(fā)現(xiàn),PID 為 29706 的 Java 進程的 CPU 飆升到 700% 多,且一直降不下來,很顯然出現(xiàn)了問題 。

接著,使用 top -Hp 命令定位線程 。使用 top -Hp 命令(其中為 Java 進程的 id 號)查看該 Java 進程內(nèi)所有線程的資源占用情況(按 shft+p 按照 cpu 占用進行排序,按 shift+m 按照內(nèi)存占用進行排序) 。在這里,我們很容易發(fā)現(xiàn),多個線程的 CPU 占用達到了 90% 多 。我們挑選線程號為 30309 的線程繼續(xù)分析 。

然后,使用 jstack 命令定位代碼 。先將線程號轉(zhuǎn)換為 16 進制,使用 printf “% x\n” 命令(tid 指線程的 id 號)將 10 進制的線程號轉(zhuǎn)換為 16 進制 。轉(zhuǎn)換后的結(jié)果為 7665,由于導出的線程快照中線程的 nid 是 16 進制的,而 16 進制以 0x 開頭,所以對應(yīng)的 16 進制的線程號 nid 為 0x7665 。再采用 jstack 命令導出線程快照,通過使用 jdk 自帶命令 jstack 獲取該 java 進程的線程快照并輸入到文件中 。最后,在生成的文件中根據(jù)線程號 nid 搜索對應(yīng)的線程描述,判斷應(yīng)該是 ImageConverter.run () 方法中的代碼出現(xiàn)問題 。

下面是 ImageConverter.run () 方法中的部分核心代碼 。這段代碼的邏輯是存儲minicap 的 socket 連接返回的數(shù)據(jù),設(shè)置阻塞隊列長度,防止出現(xiàn)內(nèi)存溢出 。在 while 循環(huán)中,不斷讀取堵塞隊列dataQueue 中的數(shù)據(jù),如果數(shù)據(jù)為空,則執(zhí)行 continue 進行下一次循環(huán) 。如果不為空,則通過 poll () 方法讀取數(shù)據(jù),做相關(guān)邏輯處理 。初看這段代碼好像沒什么問題,但是如果dataQueue對象長期為空的話,這里就會一直空循環(huán),導致 CPU 飆升 。

// 全局變量
private BlockingQueue<byte[]> dataQueue = new LinkedBlockingQueue<byte[]>(100000);

// 消費線程
@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 阻塞隊列的 API 發(fā)現(xiàn),有兩種取值的 API 。take () 方法取出隊列中的頭部元素,如果隊列為空則調(diào)用此方法的線程被阻塞等待,直到有元素能被取出,如果等待過程被中斷則拋出 InterruptedException;poll () 方法取出隊列中的頭部元素,如果隊列為空返回 null 。顯然 take 方法更適合這里的場景 。將代碼修改如下:

while (isRunning) {
    /* if (device.getMinicap().dataQueue.isEmpty()) {
         continue;
     }*/
    byte[] buffer = new byte[0];
    try {
        buffer = device.getMinicap().dataQueue.take();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    ……
}

重啟項目后,測試發(fā)現(xiàn)項目運行穩(wěn)定,對應(yīng)項目進程的 CPU 消耗占比不到 10% 。通過這個案例可以看出,在面對 Java 進程 CPU 飆升問題時,我們可以借助 top、jstack 等工具,逐步定位到問題代碼,并通過合理的代碼修改來解決問題 。

3.2案例二:UV 通道下采樣代碼優(yōu)化

在圖像和視頻處理等領(lǐng)域,常常會涉及到對圖像數(shù)據(jù)的各種操作,UV 通道下采樣就是其中常見的一種 。下面我們來看一個 UV 通道下采樣代碼從標量處理轉(zhuǎn)換為向量處理的優(yōu)化案例 。

假設(shè)我們有一個 UV 通道下采樣的任務(wù),輸入是 u8 類型的數(shù)據(jù),通過鄰近的 4 個像素求平均,輸出 u8 類型的數(shù)據(jù),達到 1/4 下采樣的目的 。我們假定每行數(shù)據(jù)長度是 16 的整數(shù)倍 。最初的 C 代碼實現(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 指令集進行優(yōu)化 。具體步驟如下:

①內(nèi)層循環(huán)向量化

內(nèi)層循環(huán)是代碼執(zhí)行次數(shù)最多的部分,因此是向量化的重點 。由于我們的輸入和輸出都是 u8 類型,NEON 寄存器 128bit,所以每次可以處理 16 個數(shù)據(jù) 。修改后的內(nèi)層循環(huán)代碼如下:

// 每次有16個數(shù)據(jù)輸出
for (i = 0; i < dst_width; i += 16) {
    //數(shù)據(jù)處理部分......
}

②數(shù)據(jù)類型和指令選擇

輸入數(shù)據(jù)加載時,UV 通道的數(shù)據(jù)是交織的,使用 vld2 指令可以實現(xiàn)解交織 。在數(shù)據(jù)處理過程中,選擇合適的指令進行計算 。例如,水平兩個數(shù)據(jù)相加可以使用 vpaddlq_u8 指令,上下兩個數(shù)據(jù)相加之后求均值可以使用 vshrn_n_u16 和 vaddq_u16 指令 。

③代碼實現(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個u8類型數(shù)據(jù)
    uint8x16x2_t v8_src0;
    //load奇數(shù)行的源數(shù)據(jù),需要兩個Q寄存器
    uint8x16x2_t v8_src1;
    //目的數(shù)據(jù)變量,需要一個Q寄存器
    uint8x8x2_t v8_dst;
    //目前只處理16整數(shù)倍部分的結(jié)果
    int32_t dst_width_align = dst_width & (-16);
    //向量化剩余的部分需要單獨處理
    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個u8結(jié)果輸出
        for (i = 0; i < dst_width_align; i += 16) {
            //提取數(shù)據(jù),進行UV分離
            v8_src0 = vld2q_u8(src_ptr0);
            src_ptr0 += 32;
            v8_src1 = vld2q_u8(src_ptr1);
            src_ptr1 += 32;
            //水平兩個數(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]);
            //上下兩個數(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é)果交織存儲
            vst2_u8(dst_ptr, v8_dst);
            dst_ptr += 16;
        }
        //process leftovers......
    }
}

通過這樣的優(yōu)化,將原本的標量處理轉(zhuǎn)換為向量處理,充分利用了 NEON 指令集的并行處理能力,大大提升了 UV 通道下采樣的效率 。在實際應(yīng)用中,對于圖像和視頻處理等對性能要求較高的場景,這種基于向量處理的優(yōu)化方式能夠顯著提高程序的運行速度,為用戶帶來更好的體驗 。

四、優(yōu)化工具大盤點

在程序 CPU 性能優(yōu)化的征程中,各類工具就像是我們的得力助手,它們能夠幫助我們精準地監(jiān)測性能指標,深入分析代碼性能瓶頸,從而為優(yōu)化工作提供有力支持。下面,我們就來詳細盤點一些常用的優(yōu)化工具。

4.1性能監(jiān)控工具

(1)top

top 是 Linux 系統(tǒng)中一個非常強大的實時監(jiān)控系統(tǒng)性能的命令行工具 。它提供了關(guān)于系統(tǒng)正在運行的進程以及系統(tǒng)總體狀態(tài)的實時動態(tài)視圖 。使用 top,我們可以看到關(guān)于 CPU 使用率、內(nèi)存使用情況、進程信息、運行時間、登錄用戶等關(guān)鍵數(shù)據(jù) 。在終端中輸入 top 并回車,即可啟動該命令,顯示系統(tǒng)當前的實時狀態(tài) 。其界面主要包括頂部區(qū)域,顯示系統(tǒng)的整體信息,如當前時間、系統(tǒng)運行時間、登錄用戶數(shù)、平均負載等;

任務(wù)(Tasks)和 CPU 狀態(tài)區(qū)域,展示當前正在運行的進程數(shù)、休眠中的進程數(shù)、停止的進程數(shù)以及僵尸進程數(shù),還有 CPU 的使用情況(用戶模式、系統(tǒng)模式、空閑等);內(nèi)存和交換空間區(qū)域,呈現(xiàn)物理內(nèi)存和交換空間的使用情況;進程列表區(qū)域,列出當前系統(tǒng)上所有進程的詳細信息,通常包括 PID(進程 ID)、用戶、優(yōu)先級、虛擬內(nèi)存使用量、物理內(nèi)存使用量、共享內(nèi)存大小、狀態(tài)(如運行、睡眠等)、CPU 使用率、內(nèi)存使用率、運行的時間以及命令行 。我們還可以使用 “-u 用戶名” 選項只顯示指定用戶的進程;“-n 次數(shù)” 指定 top 命令更新屏幕的次數(shù),之后自動退出;“-d 秒數(shù)” 設(shè)置 top 命令更新的時間間隔(以秒為單位)等 。

(2)htop

htop 是 top 的一個替代品,它提供了更加友好的交互界面,并且可以實時監(jiān)控系統(tǒng)的各項指標,包括 CPU 的使用情況 。htop 的界面更加直觀,將輸出界面劃分成了四個區(qū)域,上左區(qū)顯示了 CPU、物理內(nèi)存和交換分區(qū)的信息;上右區(qū)顯示了任務(wù)數(shù)量、平均負載和連接運行時間等信息;進程區(qū)域顯示出當前系統(tǒng)中的所有進程;操作提示區(qū)顯示了當前界面中 F1 - F10 功能鍵中定義的快捷功能 。

例如,F(xiàn)1 用于顯示幫助信息;F2 可配置界面中的顯示信息,我們可以根據(jù)自己的需要修改顯式模式以及想要顯示的內(nèi)容,比如以 LED 的形式顯示 CPU 的使用情況,并且在左邊的區(qū)域添加 hostname,在右邊的區(qū)區(qū)域添加 clock,也可以自定義進程區(qū)域中的顯示內(nèi)容;F3 用于進程搜索;F4 是進程過濾器;F5 顯示進程樹;F6 用于排序;F7 減小 nice 值;F8 增加 nice 值;F9 殺掉指定進程;F10 退出 htop 。

空格鍵用于標記選中的進程,用于實現(xiàn)對多個進程同時操作;U 取消所有選中的進程;s 顯示光標所在進程執(zhí)行的系統(tǒng)調(diào)用;l 顯示光標所在進程的文件列表;I 對排序的結(jié)果進行反轉(zhuǎn)顯示 ;a 綁定進程到指定的 CPU;u 顯示指定用戶的進程;M 按照內(nèi)存使用百分比排序,對應(yīng) MEM% 列;P 按照 CPU 使用百分比排序,對應(yīng) CPU% 列;T 按照進程運行的時間排序,對應(yīng) TIME + 列 。

(3)mpstat

mpstat 是 Multiprocessor Statistics 的縮寫,是實時系統(tǒng)監(jiān)控工具 。其報告與 CPU 的一些統(tǒng)計信息,這些信息存放在 /proc/stat 文件中 。在多 CPUs 系統(tǒng)里,其不但能查看所有 CPU 的平均狀況信息,而且能夠查看特定 CPU 的信息 。mpstat 最大的特點是可以查看多核心 cpu 中每個計算核心的統(tǒng)計數(shù)據(jù),而類似工具 vmstat 只能查看系統(tǒng)整體 cpu 情況 。其語法為 “mpstat [-P {|ALL}] [internal [count]]” ,其中 “-P {|ALL}” 表示監(jiān)控哪個 CPU, cpu 在 [0,cpu 個數(shù) - 1] 中取值;internal 是相鄰的兩次采樣的間隔時間;

count 是采樣的次數(shù),count 只能和 delay 一起使用 。當沒有參數(shù)時,mpstat 則顯示系統(tǒng)啟動以后所有信息的平均值 。有 interval 時,第一行的信息自系統(tǒng)啟動以來的平均信息,從第二行開始,輸出為前一個 interval 時間段的平均信息 。例如,“mpstat 2” 表示每 2 秒更新一次,顯示多核 CPU 核心的當前運行狀況信息;“mpstat -P ALL 2” 則可以查看每個 cpu 核心的詳細當前運行狀況信息 。

(4)pidstat

pidstat 是一個常用的進程性能分析工具,用來實時查看進程的 CPU、內(nèi)存、I/O 以及上下文切換等性能指標 。要查看所有進程的 CPU 使用情況,使用 “pidstat” 命令,其輸出結(jié)果包括 PID(進程 ID)、% usr(用戶態(tài) CPU 使用率)、% system(內(nèi)核態(tài) CPU 使用率)、% CPU(總的 CPU 使用率)等信息 。如果想在一段時間內(nèi)持續(xù)監(jiān)控進程的 CPU 使用情況,可以使用 “pidstat 2 5” 這樣的命令格式,意味著每隔 2 秒刷新一次數(shù)據(jù),共顯示 5 次 。

若要查看指定進程的 CPU 使用情況,假設(shè)進程的 PID 為 1234,可使用 “pidstat -p 1234” 。pidstat 還能查看內(nèi)存使用情況,使用 “-r” 選項,如 “pidstat -r”,將顯示 minflt/s(每秒次級頁面錯誤數(shù))、majflt/s(每秒主頁面錯誤數(shù))、VSZ(虛擬內(nèi)存大?。?、RSS(駐留集大?。┑扰c內(nèi)存相關(guān)的信息 ,同樣也可以指定時間間隔和次數(shù)來持續(xù)監(jiān)控 。此外,使用 “-d” 選項可以監(jiān)控進程的 I/O 操作,顯示 kB_rd/s(每秒從磁盤讀取的數(shù)據(jù)量)、kB_wr/s(每秒寫入磁盤的數(shù)據(jù)量)、kB_ccwr/s(取消寫入的千字節(jié)數(shù),由于緩存)等信息 ;使用 “-t” 選項可以顯示線程級別的監(jiān)控信息 。

4.2代碼分析工具

(1)perf

perf 是內(nèi)置于 Linux 內(nèi)核源碼樹中的性能剖析工具 。它基于事件采樣原理,使用了許多 Linux 跟蹤特性,可用于進行函數(shù)級與指令級的性能瓶頸的查找與熱點代碼的定位 。

例如,“perf top” 可以實時顯示系統(tǒng) / 進程的性能統(tǒng)計信息 ,常用參數(shù)包括 “-e” 指定性能事件,“-a” 顯示在所有 CPU 上的性能統(tǒng)計信息,“-C” 顯示在指定 CPU 上的性能統(tǒng)計信息,“-p” 指定進程 PID,“-t” 指定線程 TID,“-K” 隱藏內(nèi)核統(tǒng)計信息,“-U”隱藏用戶空間的統(tǒng)計信息,“-s” 指定待解析的符號信息等 。

“perf stat” 用于分析系統(tǒng) / 進程的整體性能概況 ,常用參數(shù)有 “-e” 選擇性能事件,“-i” 禁止子任務(wù)繼承父任務(wù)的性能計數(shù)器,“-r” 重復(fù)執(zhí)行 n 次目標程序,并給出性能指標在 n 次執(zhí)行中的變化范圍,“-n” 僅輸出目標程序的執(zhí)行時間,而不開啟任何性能計數(shù)器,“-a” 指定全部 cpu,“-C” 指定某個 cpu,“-A” 將給出每個處理器上相應(yīng)的信息,“-p” 指定待分析的進程 id,“-t” 指定待分析的線程 id 等 。

“perf record” 用于記錄一段時間內(nèi)系統(tǒng) / 進程的性能時間 ,常用參數(shù)包括 “-e” 選擇性能事件,“-p” 待分析進程的 id,“-t” 待分析線程的 id,“-a” 分析整個系統(tǒng)的性能,“-C” 只采集指定 CPU 數(shù)據(jù),“-c” 事件的采樣周期,“-o” 指定輸出文件,默認為 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” 顯示每個符號的地址,“-d” 只顯示指定 dos 的符號,“-C” 只顯示指定 comm 的信息(Comm. 觸發(fā)事件的進程名),“-S” 只考慮指定符號,“-U” 只顯示已解析的符號,“-g [type,min,order]” 顯示調(diào)用關(guān)系,具體等同于 perf top 命令中的 “-g”,“-c” 只顯示指定 cpu 采樣信息 。

(2)gprof

gprof 是 GNU 提供的一款性能分析工具,它可以幫助我們分析程序的性能瓶頸 。使用 gprof,我們需要在編譯程序時加上 “-pg” 選項,例如 “gcc -pg -o myprogram myprogram.c” 。編譯完成后運行程序,程序運行結(jié)束后會生成一個名為 “gmon.out” 的文件 。然后使用 “gprof” 命令加上可執(zhí)行文件名和 “gmon.out” 文件來進行分析,如 “gprof myprogram gmon.out” 。

gprof 會生成一份詳細的報告,展示函數(shù)的調(diào)用關(guān)系、每個函數(shù)的執(zhí)行時間、調(diào)用次數(shù)等信息 。通過這份報告,我們可以清楚地看到哪些函數(shù)占用了較多的執(zhí)行時間,從而有針對性地對這些函數(shù)進行優(yōu)化 。例如,如果報告顯示某個函數(shù)的執(zhí)行時間很長,且被頻繁調(diào)用,那么我們就可以深入分析該函數(shù)的代碼邏輯,嘗試優(yōu)化算法或者減少不必要的操作,以提高程序的整體性能 。

(3)valgrind

valgrind 是一套功能強大的調(diào)試和分析工具,其中的 Massif 工具可以用來分析程序的內(nèi)存使用情況,Cachegrind 工具則可以用于分析 CPU 緩存的使用情況 。使用 Massif 分析內(nèi)存時,運行程序時使用 “valgrind --tool=massif myprogram” 命令 ,程序運行結(jié)束后會生成一個名為 “massif.out.XXXX”(XXXX 為數(shù)字)的文件 。然后可以使用 “ms_print massif.out.XXXX” 命令來查看內(nèi)存使用報告,報告中會顯示程序在不同時間點的堆內(nèi)存使用量、峰值內(nèi)存使用量等信息 ,幫助我們發(fā)現(xiàn)內(nèi)存泄漏、內(nèi)存分配不合理等問題 。

使用 Cachegrind 分析 CPU 緩存時,運行程序時使用 “valgrind --tool=cachegrind myprogram” 命令 ,運行結(jié)束后會生成 “cachegrind.out.XXXX” 文件 ,通過 “cg_annotate cachegrind.out.XXXX” 命令可以查看緩存使用報告,報告中會展示函數(shù)的緩存命中率、緩存缺失次數(shù)等信息 ,讓我們了解程序?qū)?CPU 緩存的利用情況,進而通過優(yōu)化數(shù)據(jù)訪問模式、調(diào)整代碼結(jié)構(gòu)等方式提高緩存命中率,提升程序性能 。

責任編輯:武曉燕 來源: 深度Linux
相關(guān)推薦

2015-03-04 13:53:33

MySQL數(shù)據(jù)庫優(yōu)化SQL優(yōu)化

2024-01-11 08:03:52

程序圖片優(yōu)化

2009-02-10 09:47:00

應(yīng)用程序訪問權(quán)限

2010-03-03 13:51:54

2024-05-07 09:01:21

Queue 模塊Python線程安全隊列

2013-06-08 11:13:00

Android開發(fā)XML解析

2013-04-15 10:48:16

Xcode ARC詳解iOS ARC使用

2010-04-23 14:04:23

Oracle日期操作

2015-08-14 10:27:53

跳槽程序員讀書摘要

2025-04-18 03:00:00

2018-03-23 13:29:29

程序員跳槽薪資

2009-08-18 09:21:54

Windows 7效率提高系統(tǒng)優(yōu)化

2014-03-19 17:22:33

2009-12-14 14:32:38

動態(tài)路由配置

2009-10-19 15:20:01

家庭綜合布線

2009-02-20 11:43:22

UNIXfish全攻略

2010-01-14 10:02:37

2009-07-17 17:43:49

Jruby開發(fā)Web

2009-12-17 16:15:00

CCNA640-810

2010-08-25 14:36:02

DHCP服務(wù)器
點贊
收藏

51CTO技術(shù)棧公眾號