阿里云系統團隊,是由原淘寶內核組擴建而成,2013年淘寶內核組響應阿里巴巴集團的號召,整建制轉入阿里云,開始為云計算底層系統構建完善的系統支持。 阿里云系統團隊是由一群具有高度使命感和自我追求的內核開發人員組成,團隊中的大多數人,都是活躍的社區內核開發人員。目前的工作領域主要涉及(但不限于) Linux內核的內存管理、文件系統、網絡和內核維護構建,以及和內核相關聯的用戶態庫和工具。如果你對我們的工作很感興趣,歡迎加入我們,請將簡歷發送至 tao.ma at linux.alibaba.com或者boyu.mt at alibaba-inc.com。
1. 什么是 CPI ?
本小節講述為什么使用 CPI 分析程序性能的意義。如果已經非常了解 CPI 對分析程序性能的意義,可以跳過本小節的閱讀。
1.1 程序怎么樣才能跑得快 ?
理解什么是 CPI,首先讓我們思考一個問題:在一個給定的處理器上,如何才能讓程序跑得更快呢?
假設程序跑得快慢的標準是程序的執行時間,那么程序執行的快慢,就可以用如下公式來表示:
因此,要想程序跑得快,即減少程序執行時間,我們就需要在以下三個方面下功夫:
減少程序總指令數
要減少程序執行的總指令數,可能有以下手段:
算法優化;好的算法設計,可能帶來更少的指令執行數。
更高效的編譯器或者解釋器;新的編譯器或者解釋器,可能對同樣的源代碼,生成更少的機器碼。
用更底層的語言優化;這是為何 Linux 內核代碼使用 C 語言,并且還喜歡內聯匯編。
更新的處理器指令;新的處理器指令,對處理某類特殊目的運算更有幫助,而新版本編譯器最重要的工作就是,在新的處理器上,用最新的高效指令;例如,x86 SSE,AVX 指令。
這一點很容易理解,縮短 CPU 時鐘周期的時間,實際上就是要提高 CPU 的主頻。這正是 Intel 過去戰無不勝的法寶之一。今天,由于主頻的提高已經到了制造工藝的極限,CPU 時鐘周期的時間很難再繼續降低了。
減少每指令執行所需平均時鐘周期數
如何減少每指令執行所需平均 CPU 時鐘周期數呢?讓我們先從 CPU 設計角度看一下:
標量處理器 (Scalar Processor) ;一個 CPU 時鐘周期只能執行一條指令;
超標量處理器 (Superscalar Processor);一個 CPU 時鐘周期可以執行多條指令。
因此不難看出,如果使用支持超標量處理器的 CPU,利用 CPU 流水線提高指令并行度,那么就可以達到我們的目的了。流水線的并行度越高,執行效率越高,那么每指令執行所需平均時鐘周期數就會越低。
當然,流水線的并行度和效率,又取決于很多因素,例如,取指令速度,訪存速度,指令亂序執行 (Out-Of-Order Execution),分支預測執行 (Branch Prediction Execution),投機執行 (Speculative Execution)的能力。一旦流水線并行執行的能力降低,那么程序的性能就會受到影響。關于超標量處理器,流水線,亂序執行,投機執行的細節,這里不再一一贅述,請查閱相關資料。
另外,在 SMP,或者多核處理器系統里,程序還可以通過并行編程來提高指令的并行度,因此,這也是為什么今天在 CPU 主頻再難以提高的情況下,CPU 架構轉為 Multi-Core 和 Many-Core。
由于提高 CPU 主頻的同時,又要保障一個 CPU 時鐘周期可以執行更多的指令,因此處理器廠商需要不斷地提高制造工藝,降低 CPU 的芯片面積和功耗。
1.2 CPI 和 IPC
在計算機體系結構領域,經常可以看到 CPI 的使用。CPI 即 Cycle Per Instruction 的縮寫,它的含義就是每指令周期數。此外,在一些場合,也可以經常看到 IPC,即 Instruction Per Cycle,含義為每周期指令數。
因此不難得出,CPI 和 IPC 的關系為,
使用 CPI 這個定義,本文開篇用于衡量程序執行性能的公式,如果具體到單 CPU 的程序執行性能場景,實際上可以表示為:
由于受到硅材料和制造工藝的限制,處理器主頻的提高已經面臨瓶頸,因此,程序性能的提高,主要的變量在 Instruction Count 和 CPI 這兩個方面。
在 Linux 上,通過perf工具,通過 Intel 處理器提供的寄存器 (PMU),可以很容易測量一個程序的 IPC。例如,下例就可以給出 Java 程序的 IPC,8 秒多的時間里,這個 Java 程序的 IPC 是 0.54:
那么,通過 IPC,我們也可以換算出 CPI 是1/0.54,約為 1.85.
通常情況下,通過 CPI 的取值,我們可以大致判斷一個計算密集型任務,到底是 CPU 密集型的還是 Memory 密集型的:
CPI 小于 1,程序通常是 CPU 密集型的;
CPI 大于 1,程序通常是 Memory 密集型的;
1.3 重新認識 CPU 利用率
對程序員來說,判斷一個計算密集型任務運行效率的重要依據就是看程序運行時的 CPU 利用率。很多人認為 CPU 利用率高就是程序的代碼在瘋狂運行。實際上,CPU 利用率高,也有可能是 CPU 正在忙等一些資源,如訪問內存遇到了瓶頸。
一些計算密集型任務,在正常情況下,CPI 很低,性能原本很好。CPU 利用率很高。但是隨著系統負載的增加,其它任務對系統資源的爭搶,導致這些計算任務的 CPI 大幅上升,性能下降。而此時,很可能 CPU 利用率上看,還是很高的,但是這種 CPU 利用率的高,實際上體現的是 CPU 的忙等,及流水線的停頓帶來的效應。
Brendan Gregg 曾在CPU Utilization is Wrong這篇博客中指出,CPU 利用率指標需要結合 CPI/IPC 指標一起來分析。并詳細介紹了前因后果。感興趣的讀者可以自行閱讀原文,或者訂閱內核月談公眾號,閱讀我們公眾號非常靠譜的譯文。
至此,相信讀者已經清楚,在不修改二進制程序的前提下,通過 CPI 指標了解程序的運行性能,有著非常重要的意義。對于計算密集型的程序,只通過 CPU 利用率這樣的傳統指標,也無法幫助你確認你的程序的運行效率,必須將 CPU 利用率和 CPI/IPC 結合起來看,確定程序的執行效率。
1.4 如何分析 CPI/IPC 指標異常?
雖然利用perf可以很方便獲取 CPI/IPC 指標,但是想分析和優化程序高 CPI 的問題,就需要一些工具和分析方法,將 CPI 高的原因,以及與之關聯的軟件的調用棧找到,從而決定優化方向。
關于 CPI 高的原因分析,在 Intel 64 and IA-32 Architectures Optimization Reference Manual, 附錄 B 里有介紹。其中主要的思路就是按照自頂向下的方法,自頂向下排查, 4 種引起 CPI 變高的主要原因,由于本文主要是介紹 CPI 火焰圖,
對于本小節的自頂向下的分析方法,限于篇幅所限,就不詳細展開了,我們稍后會有專門的文章做詳細介紹。
2. CPI 火焰圖
Brendan Gregg 在CPI Flame Graphs: Catching Your CPUs Napping一文中,介紹了使用 CPI 火焰圖來建立 CPI 和軟件調用棧的關聯。
我們已經知道,光看 CPU 利用率并不能知道 CPU 在干嘛。因為 CPU 可能執行到一條指令就停下來,等待資源了。這種等待對軟件是透明的,因此從用戶角度看,CPU 還是在被使用狀態,但是實際上,指令并沒有有效地執行,CPU 在忙等,這種 CPU 利用率并不是有效的利用率。
要發現 CPU 在 busy 的時候實際上在干什么,最簡單的方法就是測量平均 CPI。CPI 高說明運行每條指令用了更多的周期。這些多出來的周期里面,通常是由于流水線的停頓周期 (Stalled Cycles) 造成的,例如,等待內存讀寫。
而 CPI 火焰圖,可以基于 CPU 火焰圖,提供一個可視化的基于 CPU 利用率和 CPI 指標,綜合分析程序 CPU 執行效率的方案。
下面這個 CPI 火焰圖引用自 Brendan Gregg 博客文章。
可以看到,CPI 火焰圖是基于 CPU 火焰圖,根據 CPI 的大小,在每個條加上了顏色。紅色代表指令,藍色代表流水線的停頓:火焰圖中,每個函數幀的寬度,顯示了函數或其子函數在 CPU 上的次數,和普通 CPU 火焰圖完全一樣。而顏色則顯示了函數在 CPU 上是運行 (running 紅色) 還是停頓 (stalled 藍色)。
火焰圖里,顏色范圍,從最高CPI為藍色(執行最慢的指令),到最低CPI為紅色 (執行最快的指令)。火焰圖是 SVG 格式,矢量圖,因此支持鼠標點擊縮放。
然而,Brendan Gregg 博客中的這篇博客,CPI 火焰圖是基于 FreeBSD 操作系統特有的命令生成的,而在 Linux 上,應該怎么辦呢?
3. 一個小程序
讓我們寫一個人造的小程序,展示在 Linux 下 CPI 火焰圖的使用。
這是一個最簡的小程序,其中包含如下兩個函數:
cpu_bound函數主體是 nop 指令的循環;由于 nop 指令是不訪問內存的最簡指令之一,因此該函數 CPI 一定小于 1,屬于典型的 CPU 密集型的代碼。
memory_bound函數使用_mm_clflush驅逐緩存,人為觸發程序的 L1 D-Cache Load Miss。因此該函數 CPI 必然大于 1,屬于典型的 Memory 密集型的代碼。
下面是程序的源碼:
在上述小程序運行時,我們使用如下命令生成 CPI 火焰圖,
最后生成的火焰圖如下,
可以看到,CPI 火焰圖看到的結果,是符合我們的預期的:
該程序所有的 CPU 時間,都分布在cpu_bound和memory_bound兩個函數里
同是 CPU 占用時間,但cpu_bound是紅色的,代表這個函數的指令在 CPU 上一直持續運行
而memory_bound是藍色的,代表這個函數發生了嚴重的訪問內存的延遲,導致了流水線停頓,屬于忙等
4. 一個benchmark
現在,我們可以使用 CPI 火焰圖來分析一個略真實一些的測試場景。下面的 CPI 火焰圖,來自fio的測試場景。
這個fio對 SATA 磁盤,做多進程同步 Direct IO 順序寫,可以看到:
紅顏色為標記為 CPU Bound 的函數。其中顏色最深的是_raw_spin_lock,這是自旋鎖的等待循環引起的。
藍顏色為標記為 Memory Bound 的函數。其中顏色最深的是fio測試程序的函數get_io_u,如果使用perf程序進一步分析,這個函數里發生了嚴重的 LLC Cache Miss。
因為 CPI 火焰圖是矢量圖,支持縮放,所以以上結論可以通過放大get_io_u的調用棧進一步確認,
到這里,讀者會發現,使用 CPI 火焰圖,可以很方便地做 CPU 利用率的分析,找到和定位引發 CPU 停頓的函數。一旦找到相關的函數,就可以通過perf annotate命令對引起停頓的指令作出進一步確認。并且,我們可以利用1.4小節的自頂向下分析方法,對 CPU 哪個環節產生瓶頸作出判斷。最后,結合這些信息,決定優化方向。
5. 小結
本文介紹了使用 CPI 火焰圖分析程序性能的方法。CPI 火焰圖不但展示了程序的 Call Stack 與 CPU 占用率的關聯性,而且還揭示了這些 CPU 占用率里,哪些部分是真正的有效的運行時間,哪些部分實際上是 CPU 因某些停頓造成的忙等。
系統管理員可以通過此工具發現系統存在的資源瓶頸,并且通過一些系統管理命令來緩解資源的瓶頸;例如,應用間的 Cache 顛簸干擾,可以通過將應用綁到不同的 CPU 上解決。
而應用開發者則可以通過優化相關函數,來提高程序的性能。例如,通過優化代碼減少 Cache Miss,從而降低應用的 CPI 來減少處理器因訪存停頓造成的性能問題。
"Linux閱碼場"是專業的Linux及系統軟件技術交流社區,企業和Linux人才的連接樞紐。
-
Linux
+關注
關注
87文章
11294瀏覽量
209344 -
程序
+關注
關注
117文章
3785瀏覽量
81005
原文標題:用CPI火焰圖分析Linux性能問題
文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論