Linux CPU 性能優(yōu)化指南(轉(zhuǎn)載)

本文作者:allenxguo,騰訊 QQ 音樂后臺(tái)開發(fā)工程師
來源:極客時(shí)間? 騰訊技術(shù)工程公眾號(hào)
系統(tǒng)平均負(fù)載
簡(jiǎn)介
系統(tǒng)平均負(fù)載:是處于可運(yùn)行或不可中斷狀態(tài)的平均進(jìn)程數(shù)。
可運(yùn)行進(jìn)程:使用 CPU 或等待使用 CPU 的進(jìn)程
不可中斷狀態(tài)進(jìn)程:正在等待某些 IO 訪問,一般是和硬件交互,不可被打斷(不可被打斷的原因是為了保護(hù)系統(tǒng)數(shù)據(jù)一致,防止數(shù)據(jù)讀取錯(cuò)誤)
查看系統(tǒng)平均負(fù)載
首先top
命令查看進(jìn)程運(yùn)行狀態(tài),如下:

程序狀態(tài)Status
進(jìn)程可運(yùn)行狀態(tài)為R
,不可中斷運(yùn)行為D
(后續(xù)講解 top 時(shí)會(huì)詳細(xì)說明)
top查看系統(tǒng)平均負(fù)載:

這里的load average
就表示系統(tǒng)最近 1 分鐘、5 分鐘、15 分鐘的系統(tǒng)瓶頸負(fù)載。
uptime查看系統(tǒng)瓶頸負(fù)載

查看 CPU 核信息
系統(tǒng)平均負(fù)載和 CPU 核數(shù)密切相關(guān),我們可以通過以下命令查看當(dāng)前機(jī)器 CPU 信息:
lscpu查看 CPU 信息:

cat /proc/cpuinfo查看每個(gè) CPU 核的信息:

系統(tǒng)平均負(fù)載升高的原因
一般來說,系統(tǒng)平均負(fù)載升高意味著 CPU 使用率上升。但是他們沒有必然聯(lián)系,CPU 密集型計(jì)算任務(wù)較多一般系統(tǒng)平均負(fù)載會(huì)上升,但是如果 IO 密集型任務(wù)較多也會(huì)導(dǎo)致系統(tǒng)平均負(fù)載升高但是此時(shí)的 CPU 使用率不一定高,可能很低因?yàn)楹芏噙M(jìn)程都處于不可中斷狀態(tài),等待 CPU 調(diào)度也會(huì)升高系統(tǒng)平均負(fù)載。
所以假如我們系統(tǒng)平均負(fù)載很高,但是 CPU 使用率不是很高,則需要考慮是否系統(tǒng)遇到了 IO 瓶頸,應(yīng)該優(yōu)化 IO 讀寫速度。
所以系統(tǒng)是否遇到 CPU 瓶頸需要結(jié)合 CPU 使用率,系統(tǒng)瓶頸負(fù)載一起查看(當(dāng)然還有其他指標(biāo)需要對(duì)比查看,下面繼續(xù)講解)
案例問題排查
stress
是一個(gè)施加系統(tǒng)壓力和壓力測(cè)試系統(tǒng)的工具,我們可以使用stress
工具壓測(cè)試 CPU,以便方便我們定位和排查 CPU 問題。

stress 命令使用

我們這里主要驗(yàn)證 CPU、IO、進(jìn)程數(shù)過多的問題
CPU 問題排查
使用stress -c 1
模擬 CPU 高負(fù)載情況,然后使用如下命令觀察負(fù)載變化情況:
uptime:使用uptime
查看此時(shí)系統(tǒng)負(fù)載:

mpstat:使用mpstat -P ALL 1
則可以查看每一秒的 CPU 每一核變化信息,整體和top
類似,好處是可以把每一秒(自定義)的數(shù)據(jù)輸出方便觀察數(shù)據(jù)的變化,最終輸出平均數(shù)據(jù):

由以上輸出可以得出結(jié)論,當(dāng)前系統(tǒng)負(fù)載升高,并且其中 1 個(gè)核跑滿主要在執(zhí)行用戶態(tài)任務(wù),此時(shí)大多數(shù)屬于業(yè)務(wù)工作。所以此時(shí)需要查哪個(gè)進(jìn)程導(dǎo)致單核 CPU 跑滿:
pidstat:使用pidstat -u 1
則是每隔 1 秒輸出當(dāng)前系統(tǒng)進(jìn)程、CPU 數(shù)據(jù):

top:當(dāng)然最方便的還是使用top
命令查看負(fù)載情況:

此時(shí)可以看到是stress
占用了很高的 CPU。
IO 問題排查
我們使用stress -i 1
來模擬 IO 瓶頸問題,即死循環(huán)執(zhí)行 sync 刷盤操作:uptime:使用uptime
查看此時(shí)系統(tǒng)負(fù)載:

mpstat:查看此時(shí) IO 消耗,但是實(shí)際上我們發(fā)現(xiàn)這里 CPU 基本都消耗在了 sys 即系統(tǒng)消耗上。

IO 無法升高的問題:
iowait 無法升高的問題,是因?yàn)榘咐?stress 使用的是?sync()
系統(tǒng)調(diào)用,它的作用是刷新緩沖區(qū)內(nèi)存到磁盤中。對(duì)于新安裝的虛擬機(jī),緩沖區(qū)可能比較小,無法產(chǎn)生大的 IO 壓力,這樣大部分就都是系統(tǒng)調(diào)用的消耗了。所以,你會(huì)看到只有系統(tǒng) CPU 使用率升高。解決方法是使用 stress 的下一代 stress-ng,它支持更豐富的選項(xiàng),比如stress-ng -i 1 --hdd 1 --timeout 600
(--hdd 表示讀寫臨時(shí)文件)。

pidstat:同上(略)
可以看出 CPU 的 IO 升高導(dǎo)致系統(tǒng)平均負(fù)載升高。我們使用pidstat
查找具體是哪個(gè)進(jìn)程導(dǎo)致 IO 升高的。
top:這里使用 top 依舊是最方面的查看綜合參數(shù),可以得出stress
是導(dǎo)致 IO 升高的元兇。
pidstat 沒有 iowait 選項(xiàng):可能是 CentOS 默認(rèn)的sysstat
太老導(dǎo)致,需要升級(jí)到 11.5.5 之后的版本才可用。
進(jìn)程數(shù)過多問題排查
進(jìn)程數(shù)過多這個(gè)問題比較特殊,如果系統(tǒng)運(yùn)行了很多進(jìn)程超出了 CPU 運(yùn)行能,就會(huì)出現(xiàn)等待 CPU 的進(jìn)程。使用stress -c 24
來模擬執(zhí)行 24 個(gè)進(jìn)程(我的 CPU 是 8 核)uptime:使用uptime
查看此時(shí)系統(tǒng)負(fù)載:

mpstat:同上(略)
pidstat:同上(略)
可以觀察到此時(shí)的系統(tǒng)處理嚴(yán)重過載的狀態(tài),平均負(fù)載高達(dá)?18.50。
top:我們還可以使用top
命令查看此時(shí)Running
狀態(tài)的進(jìn)程數(shù),這個(gè)數(shù)量很多就表示系統(tǒng)正在運(yùn)行、等待運(yùn)行的進(jìn)程過多。
總結(jié)
通過以上問題現(xiàn)象及解決思路可以總結(jié)出:
平均負(fù)載高有可能是 CPU 密集型進(jìn)程導(dǎo)致的
平均負(fù)載高并不一定代表 CPU 使用率高,還有可能是 I/O 更繁忙了
當(dāng)發(fā)現(xiàn)負(fù)載高的時(shí)候,你可以使用 mpstat、pidstat 等工具,輔助分析負(fù)載的來源
總結(jié)工具:mpstat
、pidstat
、top
和uptime
CPU 上下文切換
CPU 上下文:CPU 執(zhí)行每個(gè)任務(wù)都需要知道任務(wù)從哪里加載、又從哪里開始運(yùn)行,也就是說,需要系統(tǒng)事先幫它設(shè)置好 CPU 寄存器和程序計(jì)數(shù)器(Program Counter,PC)包括 CPU 寄存器在內(nèi)都被稱為 CPU 上下文。
CPU 上下文切換:CPU 上下文切換,就是先把前一個(gè)任務(wù)的 CPU 上下文(也就是 CPU 寄存器和程序計(jì)數(shù)器)保存起來,然后加載新任務(wù)的上下文到這些寄存器和程序計(jì)數(shù)器,最后再跳轉(zhuǎn)到程序計(jì)數(shù)器所指的新位置,運(yùn)行新任務(wù)。
CPU 上下文切換:分為進(jìn)程上下文切換、線程上下文切換以及中斷上下文切換。
進(jìn)程上下文切換
從用戶態(tài)切換到內(nèi)核態(tài)需要通過系統(tǒng)調(diào)用來完成,這里就會(huì)發(fā)生進(jìn)程上下文切換(特權(quán)模式切換),當(dāng)切換回用戶態(tài)同樣發(fā)生上下文切換。
一般每次上下文切換都需要幾十納秒到數(shù)微秒的 CPU 時(shí)間,如果切換較多還是很容易導(dǎo)致 CPU 時(shí)間的浪費(fèi)在寄存器、內(nèi)核棧以及虛擬內(nèi)存等資源的保存和恢復(fù)上,這里同樣會(huì)導(dǎo)致系統(tǒng)平均負(fù)載升高。
Linux 為每個(gè) CPU 維護(hù)一個(gè)就緒隊(duì)列,將 R 狀態(tài)進(jìn)程按照優(yōu)先級(jí)和等待 CPU 時(shí)間排序,選擇最需要的 CPU 進(jìn)程執(zhí)行。這里運(yùn)行進(jìn)程就涉及了進(jìn)程上下文切換的時(shí)機(jī):
進(jìn)程時(shí)間片耗盡、。
進(jìn)程在系統(tǒng)資源不足(內(nèi)存不足)。
進(jìn)程主動(dòng)
sleep
。有優(yōu)先級(jí)更高的進(jìn)程執(zhí)行。
硬中斷發(fā)生。
線程上下文切換
線程和進(jìn)程:
當(dāng)進(jìn)程只有一個(gè)線程時(shí),可以認(rèn)為進(jìn)程就等于線程。
當(dāng)進(jìn)程擁有多個(gè)線程時(shí),這些線程會(huì)共享相同的虛擬內(nèi)存和全局變量等資源。這些資源在上下文切換時(shí)是不需要修改的。
線程也有自己的私有數(shù)據(jù),比如棧和寄存器等,這些在上下文切換時(shí)也是需要保存的。
所以線程上下文切換包括了 2 種情況:
不同進(jìn)程的線程,這種情況等同于進(jìn)程切換。
通進(jìn)程的線程切換,只需要切換線程私有數(shù)據(jù)、寄存器等不共享數(shù)據(jù)。
中斷上下文切換
中斷處理會(huì)打斷進(jìn)程的正常調(diào)度和執(zhí)行,轉(zhuǎn)而調(diào)用中斷處理程序,響應(yīng)設(shè)備事件。而在打斷其他進(jìn)程時(shí),就需要將進(jìn)程當(dāng)前的狀態(tài)保存下來,這樣在中斷結(jié)束后,進(jìn)程仍然可以從原來的狀態(tài)恢復(fù)運(yùn)行。
對(duì)同一個(gè) CPU 來說,中斷處理比進(jìn)程擁有更高的優(yōu)先級(jí),所以中斷上下文切換并不會(huì)與進(jìn)程上下文切換同時(shí)發(fā)生。由于中斷會(huì)打斷正常進(jìn)程的調(diào)度和執(zhí)行,所以大部分中斷處理程序都短小精悍,以便盡可能快的執(zhí)行結(jié)束。
查看系統(tǒng)上下文切換
vmstat:工具可以查看系統(tǒng)的內(nèi)存、CPU 上下文切換以及中斷次數(shù):

cs:則為每秒的上下文切換次數(shù)。
in:則為每秒的中斷次數(shù)。
r:就緒隊(duì)列長(zhǎng)度,正在運(yùn)行或等待 CPU 的進(jìn)程。
b:不可中斷睡眠狀態(tài)的進(jìn)程數(shù),例如正在和硬件交互。
pidstat:使用pidstat -w
選項(xiàng)查看具體進(jìn)程的上下文切換次數(shù):

其中cswch/s
和nvcswch/s
表示自愿上下文切換和非自愿上下文切換。
自愿上下文切換:是指進(jìn)程無法獲取所需資源,導(dǎo)致的上下文切換。比如說, I/O、內(nèi)存等系統(tǒng)資源不足時(shí),就會(huì)發(fā)生自愿上下文切換。
非自愿上下文切換:則是指進(jìn)程由于時(shí)間片已到等原因,被系統(tǒng)強(qiáng)制調(diào)度,進(jìn)而發(fā)生的上下文切換。比如說,大量進(jìn)程都在爭(zhēng)搶 CPU 時(shí),就容易發(fā)生非自愿上下文切換
案例問題排查
這里我們使用sysbench
工具模擬上下文切換問題。
先使用vmstat 1
查看當(dāng)前上下文切換信息:

然后使用sysbench --threads=64 --max-time=300 threads run
模擬 64 個(gè)線程執(zhí)行任務(wù),此時(shí)我們?cè)俅?code>vmstat 1查看上下文切換信息:

我們可以明顯的觀察到:
當(dāng)前 cs、in 此時(shí)劇增。
sy+us 的 CPU 占用超過 90%。
r 就緒隊(duì)列長(zhǎng)度達(dá)到 16 個(gè)超過了 CPU 核心數(shù) 8 個(gè)。
分析 cs 上下文切換問題
我們使用pidstat
查看當(dāng)前 CPU 信息和具體的進(jìn)程上下文切換信息:

所以我們可以看到大量的sysbench
線程存在很多的上下文切換。
分析 in 中斷問題
我們可以查看系統(tǒng)的watch -d cat /proc/softirqs
以及watch -d cat /proc/interrupts
來查看系統(tǒng)的軟中斷和硬中斷(內(nèi)核中斷)。我們這里主要觀察/proc/interrupts
即可。

這里明顯看出重調(diào)度中斷(RES)增多,這個(gè)中斷表示喚醒空閑狀態(tài) CPU 來調(diào)度新任務(wù)執(zhí)行,
總結(jié)
自愿上下文切換變多了,說明進(jìn)程都在等待資源,有可能發(fā)生了 I/O 等其他問題。
非自愿上下文切換變多了,說明進(jìn)程都在被強(qiáng)制調(diào)度,也就是都在爭(zhēng)搶 CPU,說明 CPU 的確成了瓶頸。
中斷次數(shù)變多了,說明 CPU 被中斷處理程序占用,還需要通過查看/proc/interrupts文件來分析具體的中斷類型。
CPU 使用率
除了系統(tǒng)負(fù)載、上下文切換信息,最直觀的 CPU 問題指標(biāo)就是 CPU 使用率信息。Linux 通過/proc虛擬文件系統(tǒng)向用戶控件提供系統(tǒng)內(nèi)部狀態(tài)信息,其中/proc/stat則是 CPU 和任務(wù)信息統(tǒng)計(jì)。

這里每一列的含義如下:
user(通??s寫為 us),代表用戶態(tài) CPU 時(shí)間。注意,它不包括下面的 nice 時(shí)間,但包括了 guest 時(shí)間。
nice(通??s寫為 ni),代表低優(yōu)先級(jí)用戶態(tài) CPU 時(shí)間,也就是進(jìn)程的 nice 值被調(diào)整為 1-19 之間時(shí)的 CPU 時(shí)間。這里注意,nice 可取值范圍是 -20 到 19,數(shù)值越大,優(yōu)先級(jí)反而越低。
system(通??s寫為 sys),代表內(nèi)核態(tài) CPU 時(shí)間。
idle(通??s寫為 id),代表空閑時(shí)間。注意,它不包括等待 I/O 的時(shí)間(iowait)。
iowait(通常縮寫為 wa),代表等待 I/O 的 CPU 時(shí)間。
irq(通??s寫為 hi),代表處理硬中斷的 CPU 時(shí)間。
softirq(通??s寫為 si),代表處理軟中斷的 CPU 時(shí)間。
steal(通常縮寫為 st),代表當(dāng)系統(tǒng)運(yùn)行在虛擬機(jī)中的時(shí)候,被其他虛擬機(jī)占用的 CPU 時(shí)間。
guest(通??s寫為 guest),代表通過虛擬化運(yùn)行其他操作系統(tǒng)的時(shí)間,也就是運(yùn)行虛擬機(jī)的 CPU 時(shí)間。
guest_nice(通常縮寫為 gnice),代表以低優(yōu)先級(jí)運(yùn)行虛擬機(jī)的時(shí)間。
這里我們可以使用top
、ps
、pidstat
等工具方便的查詢這些數(shù)據(jù),可以很方便的看到 CPU 使用率很高的進(jìn)程,這里我們可以通過這些工具初步定為,但是具體的問題原因還需要其他方法繼續(xù)查找。
這里我們可以使用perf top
方便查看熱點(diǎn)數(shù)據(jù),也可以使用perf record
可以將當(dāng)前數(shù)據(jù)保存起來方便后續(xù)使用perf report
查看。
CPU 使用率問題排查
這里總結(jié)一下 CPU 使用率問題及排查思路:
用戶 CPU 和 Nice CPU 高,說明用戶態(tài)進(jìn)程占用了較多的 CPU,所以應(yīng)該著重排查進(jìn)程的性能問題。
系統(tǒng) CPU 高,說明內(nèi)核態(tài)占用了較多的 CPU,所以應(yīng)該著重排查內(nèi)核線程或者系統(tǒng)調(diào)用的性能問題。
I/O 等待 CPU 高,說明等待 I/O 的時(shí)間比較長(zhǎng),所以應(yīng)該著重排查系統(tǒng)存儲(chǔ)是不是出現(xiàn)了 I/O 問題。
軟中斷和硬中斷高,說明軟中斷或硬中斷的處理程序占用了較多的 CPU,所以應(yīng)該著重排查內(nèi)核中的中斷服務(wù)程序。
CPU 問題排查套路
CPU 使用率
CPU 使用率主要包含以下幾個(gè)方面:
用戶 CPU 使用率,包括用戶態(tài) CPU 使用率(user)和低優(yōu)先級(jí)用戶態(tài) CPU 使用率(nice),表示 CPU 在用戶態(tài)運(yùn)行的時(shí)間百分比。用戶 CPU 使用率高,通常說明有應(yīng)用程序比較繁忙。
系統(tǒng) CPU 使用率,表示 CPU 在內(nèi)核態(tài)運(yùn)行的時(shí)間百分比(不包括中斷)。系統(tǒng) CPU 使用率高,說明內(nèi)核比較繁忙。
等待 I/O 的 CPU 使用率,通常也稱為 iowait,表示等待 I/O 的時(shí)間百分比。iowait 高,通常說明系統(tǒng)與硬件設(shè)備的 I/O 交互時(shí)間比較長(zhǎng)。
軟中斷和硬中斷的 CPU 使用率,分別表示內(nèi)核調(diào)用軟中斷處理程序、硬中斷處理程序的時(shí)間百分比。它們的使用率高,通常說明系統(tǒng)發(fā)生了大量的中斷。
除在虛擬化環(huán)境中會(huì)用到的竊取 CPU 使用率(steal)和客戶 CPU 使用率(guest),分別表示被其他虛擬機(jī)占用的 CPU 時(shí)間百分比,和運(yùn)行客戶虛擬機(jī)的 CPU 時(shí)間百分比。
平均負(fù)載
反應(yīng)了系統(tǒng)的整體負(fù)載情況,可以查看過去 1 分鐘、過去 5 分鐘和過去 15 分鐘的平均負(fù)載。
上下文切換
上下文切換主要關(guān)注 2 項(xiàng)指標(biāo):
無法獲取資源而導(dǎo)致的自愿上下文切換。
被系統(tǒng)強(qiáng)制調(diào)度導(dǎo)致的非自愿上下文切換。
CPU 緩存命中率
CPU 的訪問速度遠(yuǎn)大于內(nèi)存訪問,這樣在 CPU 訪問內(nèi)存時(shí)不可避免的要等待內(nèi)存響應(yīng)。為了協(xié)調(diào) 2 者的速度差距出現(xiàn)了 CPU 緩存(多級(jí)緩存)。如果 CPU 緩存命中率越高則性能會(huì)更好,我們可以使用以下工具查看 CPU 緩存命中率,工具地址、項(xiàng)目地址 perf-tools

總結(jié)
通過性能指標(biāo)查工具(CPU 相關(guān))

根據(jù)工具查性能指標(biāo)(CPU 相關(guān))

CPU 問題排查方向
有了以上性能工具,在實(shí)際遇到問題時(shí)我們并不可能全部性能工具跑一遍,這樣效率也太低了,所以這里可以先運(yùn)行幾個(gè)常用的工具 top、vmstat、pidstat 分析系統(tǒng)大概的運(yùn)行情況然后在具體定位原因。

CPU 問題優(yōu)化方向
性能優(yōu)化往往是多方面的,CPU、內(nèi)存、網(wǎng)絡(luò)等都是有關(guān)聯(lián)的,這里暫且給出 CPU 優(yōu)化的思路,以供參考。
程序優(yōu)化
基本優(yōu)化:程序邏輯的優(yōu)化比如減少循環(huán)次數(shù)、減少內(nèi)存分配,減少遞歸等等。
編譯器優(yōu)化:開啟編譯器優(yōu)化選項(xiàng)例如
gcc -O2
對(duì)程序代碼優(yōu)化。算法優(yōu)化:降低蘇研發(fā)復(fù)雜度,例如使用
nlogn
的排序算法,使用logn
的查找算法等。異步處理:例如把輪詢改為通知方式
多線程代替多進(jìn)程:某些場(chǎng)景下多線程可以代替多進(jìn)程,因?yàn)樯舷挛那袚Q成本較低
緩存:包括多級(jí)緩存的使用(略)加快數(shù)據(jù)訪問
系統(tǒng)優(yōu)化
CPU 綁定:綁定到一個(gè)或多個(gè) CPU 上,可以提高 CPU 緩存命中率,減少跨 CPU 調(diào)度帶來的上下文切換問題
CPU 獨(dú)占:跟 CPU 綁定類似,進(jìn)一步將 CPU 分組,并通過 CPU 親和性機(jī)制為其分配進(jìn)程。
優(yōu)先級(jí)調(diào)整:使用 nice 調(diào)整進(jìn)程的優(yōu)先級(jí),適當(dāng)降低非核心應(yīng)用的優(yōu)先級(jí),增高核心應(yīng)用的優(yōu)先級(jí),可以確保核心應(yīng)用得到優(yōu)先處理。
為進(jìn)程設(shè)置資源限制:使用 Linux cgroups 來設(shè)置進(jìn)程的 CPU 使用上限,可以防止由于某個(gè)應(yīng)用自身的問題,而耗盡系統(tǒng)資源。
NUMA 優(yōu)化:支持 NUMA 的處理器會(huì)被劃分為多個(gè) Node,每個(gè) Node 有本地的內(nèi)存空間,這樣 CPU 可以直接訪問本地空間內(nèi)存。
中斷負(fù)載均衡:無論是軟中斷還是硬中斷,它們的中斷處理程序都可能會(huì)耗費(fèi)大量的 CPU。開啟 irqbalance 服務(wù)或者配置 smp_affinity,就可以把中斷處理過程自動(dòng)負(fù)載均衡到多個(gè) CPU 上。