互聯網開發的中年焦慮症處方,內功修煉CPU篇彙總,值得收藏!

最近網絡在爆炒一篇標題為《互聯網不需要中年人》,瘋狂渲染35歲的碼農的前程問題,製造焦慮。本來我覺得這個事情應該只是媒體博眼球的一個炒作而已。不過恰恰最近面試了有70多人,其中有很多工作7,8年以上的的同學。這些人裡基本上可以非常明確地劃分成兩類。第一類是雖然工作了7,8年以上了,但是所有的經驗都集中在業務層。換句話說,並不是有7-8年經驗,而是工作了7-8年而已。稍微深入問一點性能相關的問題都沒有好的思路,技術能力並沒有隨著工作年限的增長而增長。也確實為這種類型的同學的前途感到擔憂。另外一類同學都是除了業務的開發以外,還會額外抽時間注重自己的技術積累,成長的就比較好,對於他們我覺得別說35歲了,45歲也仍然會是團隊的中流砥柱。

對於第一類同學來講,技術成長過慢也不完全是他們自己的原因。現在國內的開發環境中都存在著一個普遍的矛盾,那就是排期過滿的需求開發和需要大把時間去磨練技術的矛盾。 排期過滿導致開發同學平時絕大部分的時間都是在處理各種各樣的業務邏輯和bug。很多同學的時間全部被這種需求吃掉了,導致沒有想法或精力去提升自己的底層技術能力,導致技術level並沒有隨著工作年限而同比增長。平時大家都是用各種語言進行業務邏輯的代碼編寫,無論你用的是PHP、Go、還是Java,都是屬於應用層的範疇。但是應用層是建立在物理層和內核層之上的。如果遇到一些性能問題的時候,很可能需要你對底層有足夠深的理解,才能夠有效優化性能或排除線上故障。內核和物理層的知識往往又比較難吃透,不投入足夠多的時間是難有大的突破。

我把在應用層的技術能力稱之為外功,把Linux內核、設備物理結構稱之為內功。我想大家的外功能力已經都足夠優秀了,但是一部分同學的內功能力卻嚴重不足。我賬號的名稱之所以命名為開發者內功修煉,就是想把我在內核、物理層上的一些理解思考總結分享給大家,幫助這些同學提升自己的技術木桶短板。本輪數十篇的分享中,我把我工作中關於CPU底層性能的先放了出來,後面還會有內存篇,磁盤篇和網絡篇。希望能幫助大家提升你對底層的理解,為你的中年焦慮症送上一劑良藥。

互聯網開發的中年焦慮症處方,內功修煉CPU篇彙總,值得收藏!

第一部分,CPU物理層

提起CPU,大家可能都知道它是幾核的,主頻是多少。但是作為軟件開發人員,我們應該對CPU有更為深入的理解。在這一部分的三篇文章中,分別帶大家認識了邏輯核、L1、L2、L3三級緩存,以及一個專門用來做頁緩存的TLB。

top命令裡看到的可能不是真正的物理CPU核,而有可能是Intel用超線程技術虛擬出來的核。這種核的兩個核也就大約只能抵得上不開超線程下的物理核的性能的1.3倍左右。因此大家不要top一看很多核,就覺得自己機器CPU超級厲害,你應該明確知道你的服務器上是真物理核,還是虛擬核。

CPU緩存是計算機整個存儲體系中非常重要的東西,對它們的正確使用會極大提升你的程序的性能。同時我也提供了幾個命令,帶大家查看你的服務器CPU的L1、L2、L3緩存。我們開發者應該像瞭解你自己的身高體重一樣,瞭解你服務器CPU的每類的緩存數量、大小,以及是否有和其它核有共享的情況。

除了L1、L2和L3之外,CPU還有一個特殊用途的緩存TLB緩存,專門用來加速邏輯地址到物理地址的轉換。如果想極致提升你的程序性能,你要學會用Perf命令還統計TLB的緩存命中率。如果有必要,可以嘗試使用內核提供的大內存頁。

第二部分,CPU內核層

在認識完CPU物理層的一些知識點後,我們緊接著就上升到了內核層來了解CPU開銷。在內核層裡,主要有三個東西比較吃CPU,你的代碼也時時刻刻在使用它們,只不過它一直是你最熟悉的陌生人而已。如果使用不當,它們會對你的應用程序產生重大影響。他們分別是系統調用、上下文切換和軟中斷。

對於性能開銷我所奉行的原則是不光要知道它開銷大,而是要知道它具體多大,要用數字能描述出來才算。對於CPU開銷來說,最為準確的方式應該是用CPU的週期數。但是週期數的一個問題是在於讀者很難有感性的理解。比如我要是和你說進程上下文切換用了12000個CPU週期,你是不是仍然感覺疑惑,12000個週期是多長?因此本文中我用時間單位來描述CPU開銷,我和你說進程上下文切換需要5us,你立馬理解了。不過這裡一個小問題是這個時間直接套用在你的機器上可能會略有出入,因此只是給你提供的一個近似參考值,並非精確。

1)系統調用

如果我們的代碼需要進行IO操作、內存操作或者是獲取一個系統時間,都會使用到系統調用。無論你的代碼是Java、Go還是PHP,使用strace命令都可以看到系統調用的執行情況。我們用了多種方式來測量其開銷,得到的基本結論是正常情況下開銷大約是200ns-15us之間。

在這個文章裡,我給大家分享了一個2500us的變態connect系統調用的例子。這就是關於系統調用的第二個結論。這是由於系統調用進入系統態後要做的工作不一樣,所處的當前環境不一樣,單次開銷的差別會比較大。要學會查看你的服務在系統調用上到底花掉了多少開銷。

2)上下文切換

進程上下文切換無時無刻不在發生,你的服務器性能低下有沒有可能是由於進程上下文切換太頻繁導致的呢?你要學會查看你的服務的上下文切換次數,以及要在心中對單次切換的CPU花銷有數。單次進程上下文切換我們通過多個實驗的方式統計到每次大約需要3-5us。在這時間裡包括了進程切換的時候保存前一個進程上下文、加載下一個進程上下文,以及刷新TLB,甚至也考慮到了切換帶來的L1、L2等緩存miss造成的額外開銷。

如果你的後端服務是被上下文切換吃去了太多的CPU,那麼可能你需要在你的技術方案中引入協程了。通過實驗數據可以看出,它的上下文切換開銷大約只有進程切換開銷的三十分之一。

3)軟中斷

Linux網絡IO在底層是用軟中斷來實現的。只要你的機器上執行了網絡IO,就必然會設計到它,你不要把人家用了都不知道人家的名字。你的服務器上內核每秒要執行多少次軟中斷,每次需要多大的開銷,也是要做到心中有數。vmstat可以查看到軟中斷的當前執行次數,單次軟中斷的CPU開銷大約是3.5us左右。基本上和進程上下文切換的開銷差不太多,在一個數量級上。

Linux還有一個關於軟中斷的特點,就是一箇中斷號的軟中斷都是在一個CPU上執行的。如果你的網絡IO密集到一個核已經處理不過來的時候,你很可能需要了解irqbalance和多隊列網卡。

4)代碼實踐

這一個非常簡單的例子,是打通你應用層到內核態任督二脈的關鍵武器。系統調用、軟中斷、上下文切換這些概念,在我介紹完了之後你可能還是感覺有點虛幻。但其實即使是一行redis的get請求,這三種開銷可能都會被用到。

第三部分、CPU用戶態

內核態和物理層是本人想重點分享的內容,但是在用戶態我也舉了一個例子,那就是大家天天在用的函數。

函數你天天在用,但是它給你造成的開銷,我想大部分人是心裡沒數的,我也是經過實踐測試才有了理性的認識。其實用戶態還有很多高開銷的操作,比如正則匹配、加解密、計算md5等等。以後如果有機會我們再分享。

結語

好了,開發內功修煉之CPU篇的分享就到此結束。

接下來我繼續帶領大家深挖內存,敬請期待!


分享到:


相關文章: