==介紹==
本Blog開始介紹一下在Linux分析性能瓶頸的基本方法。主要圍繞一個(gè)基本的分析模型,介紹perf和ftrace的使用技巧,然后東一扒子,西一扒子,逮到什么說什么,也不一定會嚴(yán)謹(jǐn)。主要是把這個(gè)領(lǐng)域的一些思路和技巧串起來。如果讀者來討論得多,我們就討論深入一點(diǎn),如果討論得少,那就當(dāng)作一個(gè)提綱給我做一些對外交流用吧。
==基本模型==
我們需要有一套完整的方法來發(fā)現(xiàn)系統(tǒng)的瓶頸。“瓶頸”這個(gè)概念,來自業(yè)務(wù)目標(biāo),不考慮業(yè)務(wù)目標(biāo)就沒有“瓶頸”這個(gè)說法。從業(yè)務(wù)目標(biāo)角度,通常我們的瓶頸出現(xiàn)在業(yè)務(wù)的通量(Throughput)和時(shí)延(Latency)兩個(gè)問題上。(手機(jī)等領(lǐng)域常常還會考慮功耗這個(gè)要素,但那個(gè)需要另外的模型,這里暫時(shí)忽略)
比如一個(gè)MySQL數(shù)據(jù)庫,你從其他機(jī)器上對它發(fā)請求,每秒它能處理10萬個(gè)請求,這個(gè)就是通量性能,每個(gè)請求的反應(yīng)時(shí)間是0.5ms,這個(gè)就是時(shí)延性能。
通量和時(shí)延是相互相承的兩個(gè)量,當(dāng)通量達(dá)到系統(tǒng)上限,時(shí)延就會大幅提高。我們從下面這個(gè)例子開始來看這個(gè)模型(圖1):
請求從客戶端發(fā)到計(jì)算機(jī)上,花t1的時(shí)間,用t2的時(shí)間完成計(jì)算,然后用t3的時(shí)間把結(jié)果送回到客戶端,這個(gè)時(shí)延是t1+t2+t3,如果我們在t3發(fā)過來后,發(fā)下一個(gè)請求,這樣系統(tǒng)的通量就是1/(t1+t2+t3)。
當(dāng)然,現(xiàn)實(shí)中我們不可能使用這樣的方法來處理業(yè)務(wù)請求,這樣處理通量肯定是很低的。因?yàn)閠2和t1/t3作用在兩個(gè)不同的運(yùn)行部件上,完全沒有必要讓他們串行(在同一個(gè)會話中有必要,但整體上沒有必要。其原理就是CPU流水線)。所以這個(gè)模型應(yīng)該這樣來實(shí)現(xiàn)(圖2):
t2變成一個(gè)隊(duì)列的處理,這樣時(shí)延還是會等于t1+t2+t3,但t2的含義變了。只要調(diào)度能平衡,而且認(rèn)為通訊管道的流量無限,通量可以達(dá)到1/t2(和CPU流水線中,一個(gè)節(jié)拍可以執(zhí)行一條指令的原理相同)。1/t2是CPU清隊(duì)列庫存的速度,數(shù)據(jù)無限地供給到隊(duì)列上,如果瞬時(shí)隊(duì)列的長度是len,CPU處理一個(gè)包的時(shí)間是ti,t2=len*ti,如果請求無限上升,len就會越來越長,時(shí)延就會變大,所以,通常我們對隊(duì)列進(jìn)行流控,流控有很多方法,反壓丟包都可以,但最終,當(dāng)系統(tǒng)進(jìn)入穩(wěn)態(tài)的時(shí)候,隊(duì)列的長度就會維持在一個(gè)穩(wěn)態(tài),這時(shí)的t2就是可以被計(jì)算的了。
對于上面這樣一個(gè)簡單模型,如果CPU占用率沒有到100%,時(shí)延會穩(wěn)定在len*ti。len其實(shí)就等于接收線程(或者中斷)一次收入的請求數(shù)。但如果CPU達(dá)到100%,隊(duì)列的長度就無限增加,時(shí)延也會跟著無限增加。所以我們前面說,當(dāng)通量達(dá)到上限的時(shí)候,時(shí)延會無限增加,直到發(fā)生流控。
現(xiàn)代CPU是一個(gè)多核多部件系統(tǒng),這種隊(duì)列關(guān)系在整個(gè)系統(tǒng)中會變得非常復(fù)雜,比如,它有可能是這樣的(圖3):
雖然很多系統(tǒng)看起來不是這樣一個(gè)接一個(gè)隊(duì)列的模型,但其實(shí)如果你只考慮主業(yè)務(wù)流,幾乎大部分情形都是這樣的
對這樣的系統(tǒng),我們?nèi)杂腥缦陆Y(jié)論:系統(tǒng)的時(shí)延等于路徑上所有隊(duì)列的len[i]*t[i]的和。其實(shí)你會發(fā)現(xiàn),這個(gè)模型和前一個(gè)簡單模型本質(zhì)上并沒有區(qū)別。只是原來的原則作用在了所有的隊(duì)列上。
也就是說:如果CPU沒有占滿,隊(duì)列也沒有達(dá)到流控,則時(shí)延會穩(wěn)定在len[i]*t[i]上。我們只要保證輸入短可以供數(shù)據(jù)到系統(tǒng)中即可
這個(gè)結(jié)論為我們的性能優(yōu)化提供了依據(jù)。就是說,我們可以先看CPU是否滿載,滿載就優(yōu)化CPU,沒有滿載就找流控點(diǎn),通過流控點(diǎn)調(diào)整時(shí)延和通量就可以了。
在多核的情況下,CPU無法滿載還會有一個(gè)原因,就是業(yè)務(wù)線程沒有辦法在多個(gè)CPU上展開,這需要通過增加處理線程來實(shí)現(xiàn)。
==全系統(tǒng)的線程模型==
我們一般看系統(tǒng)是從“模塊”的角度來看的,但看系統(tǒng)的性能模型,我們是從線程的角度來看的。
這里的線程是廣義線程,表示所有CPU可以調(diào)度以使用自己的執(zhí)行能力的實(shí)體,包括一般意義的線程,中斷,signal_handler等。
好比前面的MySQL的模型,請求從網(wǎng)卡上發(fā)送過來。首先是中斷向量收到包請求,然后是softirq收包,調(diào)用napi的接口來收包,比如napi_schedule。這時(shí)調(diào)用進(jìn)入網(wǎng)卡框架層了,但線程上下文還是沒有變,napi_schedule回過頭調(diào)用網(wǎng)卡的polling函數(shù),網(wǎng)卡收包,通過napi_skb_finish()一類的函數(shù)向IP層送包,然后順著比如netif_receive_skb_internal->__netif_receive_skb->__netif_receive_skb_core->deliver_skb->...這樣的路徑一路進(jìn)去到某種類型的socket buffer中,這個(gè)過程跨越多個(gè)模塊,跨越多個(gè)協(xié)議棧的層,甚至在極端情況下可以跨越內(nèi)核態(tài)和用戶態(tài)。但我們?nèi)哉J(rèn)為是一個(gè)線程搬移,是softirq線程把網(wǎng)卡上的buffer,搬移到CPU socket buffer的一個(gè)過程。調(diào)整網(wǎng)卡和socket buffer之間的大小,流控時(shí)間,部署給這些隊(duì)列的線程的數(shù)目和調(diào)度時(shí)間,就可以調(diào)整好整個(gè)系統(tǒng)的性能。
從線程模型上看性能,處理模型就會比較簡單:我們?nèi)绻M岣咭粋€(gè)隊(duì)列清庫存的效率,只要增加在這個(gè)隊(duì)列上的搬移線程,或者提高部署在上面的線程(實(shí)際上是CPU核)的數(shù)量,就可以平衡它的流控時(shí)間。如果我們能平衡所有隊(duì)列的效率和長度,我們就比較容易控制整個(gè)系統(tǒng)的通量和時(shí)延了。
這其中當(dāng)然還會涉及很多細(xì)節(jié)的設(shè)計(jì)技巧,但大方向是這個(gè)。
在線程這個(gè)問題上,最后要補(bǔ)充幾句:線程是為了驅(qū)動(dòng)系統(tǒng)的運(yùn)行。不少新手很容易把線程和模塊的概念搞到一起,甚至給每個(gè)模塊配備一個(gè)線程。這是不少系統(tǒng)調(diào)度性能差的原因。初學(xué)者應(yīng)該要時(shí)刻提醒自己,線程和模塊是兩個(gè)獨(dú)立的,正交的概念,是不應(yīng)該綁定的。模塊是為了實(shí)現(xiàn)上的內(nèi)聚,而線程是為了:
1. 把計(jì)算壓力分布到多個(gè)核上
2. 匹配不同執(zhí)行流程的速率
3. 當(dāng)線程的執(zhí)行流程中有同步IO的時(shí)候,增加線程以便減少在同步IO上的等待時(shí)間(本質(zhì)上是增加流水線層次提升執(zhí)行部件的同步效率)
后面我們談具體的優(yōu)化技巧的時(shí)候我們會重新提到線程的這些效果。
==一般操作方法==
所以,當(dāng)我們遭遇一個(gè)通量性能瓶頸的時(shí)候,我們通常分三步來發(fā)現(xiàn)瓶頸的位置:
1. CPU占用率是否已經(jīng)滿了,這個(gè)用top就可以看到,比如下面這個(gè)例子:
有兩個(gè)CPU的idle為0,另兩個(gè)基本上都是接近100%的idle,我們基于這個(gè)就可以決定我們下一步的分析方向是什么了。
如果CPU沒有滿,有三種可能:
1.1 某個(gè)隊(duì)列提早流控了。我們通過查看每個(gè)獨(dú)立隊(duì)列的統(tǒng)計(jì)來尋找這些流控的位置,決定是否需要提升隊(duì)列長度來修復(fù)這種流控。例如,我們常常用ifconfig來觀察網(wǎng)卡是否有丟包:
wlan0 Link encap:以太網(wǎng) 硬件地址 7c:7a:91:xx:xx:xx
inet 地址:192.168.0.103 廣播:192.168.0.255 掩碼:255.255.255.0
inet6 地址: fe80::7e7a:91ff:fefe:5a1a/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 躍點(diǎn)數(shù):1
接收數(shù)據(jù)包:113832 錯(cuò)誤:0 丟棄:0 過載:0 幀數(shù):0
發(fā)送數(shù)據(jù)包:81183 錯(cuò)誤:0 丟棄:0 過載:0 載波:0
碰撞:0 發(fā)送隊(duì)列長度:1000
接收字節(jié):47850861 (47.8 MB) 發(fā)送字節(jié):18914031 (18.9 MB)
在高速網(wǎng)卡場景中,我們常常要修改/proc/sys中的網(wǎng)絡(luò)參數(shù)保證收包緩沖區(qū)足夠處理一波netpolling的沖擊。
我們自己寫業(yè)務(wù)程序的時(shí)候,也應(yīng)該對各個(gè)隊(duì)列的水線,丟包數(shù)等信息進(jìn)行統(tǒng)計(jì),這樣有助于我們快速發(fā)現(xiàn)隊(duì)列的問題。
1.2 調(diào)度沒有充分展開,比如你只有一個(gè)線程,而你其實(shí)有16個(gè)核,這樣就算其他核閑著,你也不能怎么樣。這是需要想辦法把業(yè)務(wù)hash展開到多個(gè)核上處理。上面那個(gè)top的結(jié)果就是這種情形。
1.3 配套隊(duì)列的線程有IO空洞,要通過異步設(shè)計(jì)把空洞填掉,或者通過在這個(gè)隊(duì)列上使用多個(gè)線程把空洞修掉。具體的原理,后面談分析方法的時(shí)候再深入介紹。
2. 如果CPU占用率已經(jīng)占滿了,觀察CPU的時(shí)間是否花在業(yè)務(wù)進(jìn)程上,如果不是,分析產(chǎn)生這種問題的原因。Linux的perf工具常??梢蕴峁┝己玫姆治觯热邕@樣:
這里例子中的CPU占用率已經(jīng)全部占滿了。但時(shí)間中只有15.43%落在主業(yè)務(wù)流程上,下面有大量的時(shí)間花在了鎖和調(diào)度上。如果我們簡單修改一下隊(duì)列模式,我們就可以把這個(gè)占用率提升到23.39%:
當(dāng)我們發(fā)現(xiàn)比如schedule調(diào)度特別頻繁的時(shí)候,我們可以通過ftrace觀察每次切換的原因,比如下面這樣:
你可以看到業(yè)務(wù)線程執(zhí)行3個(gè)ns就直接切換為Idle了,我們可以在業(yè)務(wù)線程上加mark看具體是什么流程導(dǎo)致這個(gè)切換的(如果系統(tǒng)真的忙,任何線程都應(yīng)該用完自己的時(shí)間片,否則就是有額外的問題引起額外的代價(jià)了)
3. 如果CPU的時(shí)間確實(shí)都已經(jīng)在處理業(yè)務(wù)的,剩下的問題就是看CPU執(zhí)行系統(tǒng)是否被充分利用(比如基于例如Top Down模型分析系統(tǒng)CPU的執(zhí)行部件是否被充分利用) 或者軟件算法是否可以優(yōu)化了。這個(gè)也可以通過perf和ftrace組合功能來實(shí)現(xiàn)。
在后面幾篇博文中,我們會逐一看看Linux的perf和ftrace功能如何對這些分析提供技術(shù)支持的。
==關(guān)于流控==
流控是個(gè)很復(fù)雜的問題,這里沒有準(zhǔn)備展開,但需要補(bǔ)充幾個(gè)值得考量的問題。
第一,流控應(yīng)該出現(xiàn)在隊(duì)列鏈的最前面,而不是在隊(duì)列的中間。因?yàn)榧词鼓阍谥虚g丟包了,前面幾個(gè)隊(duì)列已經(jīng)浪費(fèi)CPU時(shí)間在這些無效的包上了,這樣的流控很低效。所以,中間的隊(duì)列,只要可能,一般不設(shè)置自身的流控
第二,在有流控的系統(tǒng)上,需要注意一種很常見的陷阱:就是隊(duì)列過長,導(dǎo)致最新的包被排到很后才處理,等完成整個(gè)系統(tǒng)的處理的時(shí)候,這個(gè)包已經(jīng)被請求方判斷超時(shí)了。這種情況會導(dǎo)致大量的失效包。所以,流控的開始時(shí)間要首先于每個(gè)包的超時(shí)時(shí)間。
==總結(jié)==文章來源:http://www.zghlxwxcb.cn/news/detail-573542.html
性能分析應(yīng)該是一個(gè)有針對性的工作,我們大部分情況都不可能通過“調(diào)整這個(gè)參數(shù)看看結(jié)果,再調(diào)整調(diào)整那個(gè)參數(shù)看一個(gè)結(jié)果,然后寄望于運(yùn)氣。我們首先必須從一開始就建立系統(tǒng)的運(yùn)行模型,并有意識地通過程序本身的統(tǒng)計(jì)以及系統(tǒng)的統(tǒng)計(jì),對程序進(jìn)行profiling,并針對性地解決問題。文章來源地址http://www.zghlxwxcb.cn/news/detail-573542.html
到了這里,關(guān)于在Linux下做性能分析1:基本模型的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!