成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

解鎖程序性能密碼:CPU優化全攻略

開發 開發工具
valgrind 是一套功能強大的調試和分析工具,其中的 Massif 工具可以用來分析程序的內存使用情況,Cachegrind 工具則可以用于分析 CPU 緩存的使用情況 。

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

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

一、深入剖析CPU性能指標

在程序的CPU性能優化之旅中,了解關鍵的CPU性能指標是至關重要的一步。這些指標如同精密儀器上的刻度,精準地反映出CPU的工作狀態和程序對其資源的利用效率,為我們的優化工作提供了清晰的方向和有力的依據 。

1.1CPU 使用率:系統狀態的晴雨表

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

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

1.2用戶進程與內核進程的CPU消耗

用戶進程是我們日常編寫和運行的程序代碼,如各種業務邏輯、庫函數的調用等。當我們運行一個數據分析程序時,數據的讀取、計算、處理等操作都屬于用戶進程的范疇,這些操作會消耗一定的 CPU 資源。而內核進程則主要負責管理系統資源,如內存的分配與回收、文件系統的操作、網絡通信的處理等。像內存拷貝、系統調用等操作都屬于內核進程的工作,它們同樣會占用 CPU 時間。

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

1.3平均負載與上下文切換

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

在 Linux 系統中,我們可以使用 uptime 命令查看平均負載,命令輸出的最后三個數字分別表示過去 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 核心數時,系統可能會出現性能問題,就像一個只能容納 100 人的候車大廳,卻來了 200 人,必然會導致擁擠和混亂。

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

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

二、優化策略大揭秘

2.1算法與數據結構的優化選擇

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

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

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

2.2編寫編譯器友好型代碼

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

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

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

除了這些常規的優化級別,GCC 還提供了一些特殊選項 :

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

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

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

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

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

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

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

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

副作用則是指函數除了返回值之外,還會對外部狀態產生影響,比如修改全局變量、進行 I/O 操作等 。例如:

long counter = 0;
long f() {
    return counter++;
}
long func1() {
    return f() + f() + f() + f();
}
long func2() {
    return 4 * f();
}

從表面上看,func1 和 func2 的結果應該是相同的,但由于 f 函數存在副作用,會修改全局變量 counter,所以這兩個函數的實際行為和返回值是不同的 。大多數編譯器不會輕易判斷一個函數是否有副作用,為了保證程序的正確性,它們通常會假設函數存在副作用,從而限制了一些優化策略 。

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

2.3基于硬件特性的深度優化

(1)利用緩存機制

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

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

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

(2)向量化編程

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

以 NEON 指令集為例,它是 ARM 架構中的一種 SIMD 擴展,被廣泛應用于移動設備和嵌入式系統中 。在圖像、音頻處理等領域,NEON 指令集有著出色的表現 。在圖像濾波處理中,需要對圖像中的每個像素點進行計算,傳統的方法是逐個像素點處理,效率較低 。而使用 NEON 指令集,可以將多個像素點的數據打包成一個 128 位的向量,然后通過一條指令對這些像素點同時進行濾波計算,就像一群人同時完成多項任務,大大提高了處理效率 。以下是一個簡單的 NEON 指令集實現兩個浮點數組相加的示例代碼:

#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數組中加載4個浮點數到NEON寄存器
        float32x4_t a = vld1q_f32(&A[i]);
        float32x4_t b = vld1q_f32(&B[i]);
        // 對兩個向量進行加法運算
        float32x4_t c = vaddq_f32(a, b);
        // 將結果存儲回C數組
        vst1q_f32(&C[i], c);
    }
}

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

三、實戰案例解析

3.1案例一:Java 進程 CPU 飆升優化

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

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

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

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

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

下面是 ImageConverter.run () 方法中的部分核心代碼 。這段代碼的邏輯是存儲minicap 的 socket 連接返回的數據,設置阻塞隊列長度,防止出現內存溢出 。在 while 循環中,不斷讀取堵塞隊列dataQueue 中的數據,如果數據為空,則執行 continue 進行下一次循環 。如果不為空,則通過 poll () 方法讀取數據,做相關邏輯處理 。初看這段代碼好像沒什么問題,但是如果dataQueue對象長期為空的話,這里就會一直空循環,導致 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 發現,有兩種取值的 API 。take () 方法取出隊列中的頭部元素,如果隊列為空則調用此方法的線程被阻塞等待,直到有元素能被取出,如果等待過程被中斷則拋出 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();
    }
    ……
}

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

3.2案例二:UV 通道下采樣代碼優化

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

假設我們有一個 UV 通道下采樣的任務,輸入是 u8 類型的數據,通過鄰近的 4 個像素求平均,輸出 u8 類型的數據,達到 1/4 下采樣的目的 。我們假定每行數據長度是 16 的整數倍 。最初的 C 代碼實現如下:

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;
        }
    }
}

為了提升代碼性能,我們可以將其轉換為向量處理,利用 NEON 指令集進行優化 。具體步驟如下:

①內層循環向量化

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

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

②數據類型和指令選擇

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

③代碼實現

#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偶數行的源數據,2組每組16個u8類型數據
    uint8x16x2_t v8_src0;
    //load奇數行的源數據,需要兩個Q寄存器
    uint8x16x2_t v8_src1;
    //目的數據變量,需要一個Q寄存器
    uint8x8x2_t v8_dst;
    //目前只處理16整數倍部分的結果
    int32_t dst_width_align = dst_width & (-16);
    //向量化剩余的部分需要單獨處理
    int32_t remain = dst_width & 15;
    int32_t i = 0;
    //外層高度循環,逐行處理
    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;
        //內層循環,一次16個u8結果輸出
        for (i = 0; i < dst_width_align; i += 16) {
            //提取數據,進行UV分離
            v8_src0 = vld2q_u8(src_ptr0);
            src_ptr0 += 32;
            v8_src1 = vld2q_u8(src_ptr1);
            src_ptr1 += 32;
            //水平兩個數據相加
            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]);
            //上下兩個數據相加,之后求均值
            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通道結果交織存儲
            vst2_u8(dst_ptr, v8_dst);
            dst_ptr += 16;
        }
        //process leftovers......
    }
}

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

四、優化工具大盤點

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

4.1性能監控工具

(1)top

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

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

(2)htop

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

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

空格鍵用于標記選中的進程,用于實現對多個進程同時操作;U 取消所有選中的進程;s 顯示光標所在進程執行的系統調用;l 顯示光標所在進程的文件列表;I 對排序的結果進行反轉顯示 ;a 綁定進程到指定的 CPU;u 顯示指定用戶的進程;M 按照內存使用百分比排序,對應 MEM% 列;P 按照 CPU 使用百分比排序,對應 CPU% 列;T 按照進程運行的時間排序,對應 TIME + 列 。

(3)mpstat

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

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

(4)pidstat

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

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

4.2代碼分析工具

(1)perf

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

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

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

“perf record” 用于記錄一段時間內系統 / 進程的性能時間 ,常用參數包括 “-e” 選擇性能事件,“-p” 待分析進程的 id,“-t” 待分析線程的 id,“-a” 分析整個系統的性能,“-C” 只采集指定 CPU 數據,“-c” 事件的采樣周期,“-o” 指定輸出文件,默認為 perf.data,“-A” 以 append 的方式寫輸出文件,“-f” 以 OverWrite 的方式寫輸出文件,“-g” 記錄函數間的調用關系 。

“perf report” 則用于讀取 perf record 生成的數據文件,并顯示分析數據 ,常用參數有 “-i” 輸入的數據文件,“-v” 顯示每個符號的地址,“-d” 只顯示指定 dos 的符號,“-C” 只顯示指定 comm 的信息(Comm. 觸發事件的進程名),“-S” 只考慮指定符號,“-U” 只顯示已解析的符號,“-g [type,min,order]” 顯示調用關系,具體等同于 perf top 命令中的 “-g”,“-c” 只顯示指定 cpu 采樣信息 。

(2)gprof

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

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

(3)valgrind

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

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

責任編輯:武曉燕 來源: 深度Linux
相關推薦

2015-03-04 13:53:33

MySQL數據庫優化SQL優化

2024-01-11 08:03:52

程序圖片優化

2010-03-03 13:51:54

2009-02-10 09:47:00

應用程序訪問權限

2010-04-23 14:04:23

Oracle日期操作

2024-05-07 09:01:21

Queue 模塊Python線程安全隊列

2013-06-08 11:13:00

Android開發XML解析

2013-04-15 10:48:16

Xcode ARC詳解iOS ARC使用

2018-03-23 13:29:29

程序員跳槽薪資

2015-08-14 10:27:53

跳槽程序員讀書摘要

2025-04-18 03:00:00

2009-08-18 09:21:54

Windows 7效率提高系統優化

2009-02-20 11:43:22

UNIXfish全攻略

2010-01-14 10:02:37

2014-03-19 17:22:33

2009-12-14 14:32:38

動態路由配置

2009-10-19 15:20:01

家庭綜合布線

2010-11-15 16:20:33

Oracle系統優化

2009-10-12 15:06:59

2009-07-04 11:26:12

unix應急安全攻略
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 91高清视频在线观看 | 一区二区国产在线观看 | 91色综合| 国产精品视频网站 | 欧美中文在线 | 福利成人 | 蜜桃精品视频在线 | 一区二区三区亚洲视频 | 国产伦精品一区二区三区视频金莲 | 国产精品国产精品国产专区不片 | 欧美日韩在线免费 | 久久精品国产久精国产 | 羞羞在线观看视频 | 99久久精品国产一区二区三区 | 日韩精品在线播放 | 国产欧美精品一区二区 | 亚洲精品二区 | 亚洲国产成人精品久久 | 九一视频在线播放 | 先锋资源网 | 国产福利网站 | 国产在线精品一区 | 国产综合第一页 | 一级日韩 | 日本黄色大片免费 | 日韩av免费在线观看 | 中文字幕在线免费观看 | 亚洲精品9999 | 一区二区三区四区av | 精品国产欧美一区二区三区成人 | 中文字幕一区二区三区不卡 | 九九亚洲 | 国产一二区免费视频 | 亚洲欧美精品在线 | 欧美一区二区在线观看 | 日韩精品中文字幕一区二区三区 | 国产精品一区二区在线 | 中文字幕在线人 | 日韩视频一级 | 午夜男人免费视频 | 欧洲精品在线观看 |