模塊三 GO語言實戰與應用-程序性能分析基礎(上)


Go 語言為程序開發者們提供了豐富的性能分析 API,和非常好用的標准工具。這些 API 主要存在於:

  1. runtime/pprof;
  2. net/http/pprof;
  3. runtime/trace;

這三個代碼包中。

另外,runtime代碼包中還包含了一些更底層的 API。它們可以被用來收集或輸出 Go 程序運行過程中的一些關鍵指標,並幫助我們生成相應的概要文件以供后續分析時使用。

至於標准工具,主要有go tool pprof和go tool trace這兩個。它們可以解析概要文件中的信息,並以人類易讀的方式把這些信息展示出來。

此外,go test命令也可以在程序測試完成后生成概要文件。如此一來,我們就可以很方便地使用前面那兩個工具讀取概要文件,並對被測程序的性能加以分析。這無疑會讓程序性能測試的一手資料更加豐富,結果更加精確和可信。

在 Go 語言中,用於分析程序性能的概要文件有三種,分別是:CPU 概要文件(CPU Profile)、內存概要文件(Mem Profile)和阻塞概要文件(Block Profile)。

這些概要文件中包含的都是:在某一段時間內,對 Go 程序的相關指標進行多次采樣后得到的概要信息。

對於 CPU 概要文件來說,其中的每一段獨立的概要信息都記錄着,在進行某一次采樣的那個時刻,CPU 上正在執行的 Go 代碼。

而對於內存概要文件,其中的每一段概要信息都記載着,在某個采樣時刻,正在執行的 Go 代碼以及堆內存的使用情況,這里包含已分配和已釋放的字節數量和對象數量。至於阻塞概要文件,其中的每一段概要信息,都代表着 Go 程序中的一個 goroutine 阻塞事件。

注意,在默認情況下,這些概要文件中的信息並不是普通的文本,它們都是以二進制的形式展現的。如果你使用一個常規的文本編輯器查看它們的話,那么肯定會看到一堆“亂碼”。

這時就可以顯現出go tool pprof這個工具的作用了。我們可以通過它進入一個基於命令行的交互式界面,並對指定的概要文件進行查閱。就像下面這樣:

$ go tool pprof cpuprofile.out
Type: cpu
Time: Nov 9, 2018 at 4:31pm (CST)
Duration: 7.96s, Total samples = 6.88s (86.38%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) 

關於這個工具的具體用法,我就不在這里贅述了。在進入這個工具的交互式界面之后,我們只要輸入指令help並按下回車鍵,就可以看到很詳細的幫助文檔。

我們現在來說說怎樣生成概要文件。

你可能會問,既然在概要文件中的信息不是普通的文本,那么它們到底是什么格式的呢?一個對廣大的程序開發者而言,並不那么重要的事實是,它們是通過 protocol buffers 生成的二進制數據流,或者說字節流。

概括來講,protocol buffers 是一種數據序列化協議,同時也是一個序列化工具。它可以把一個值,比如一個結構體或者一個字典,轉換成一段字節流。

也可以反過來,把經過它生成的字節流反向轉換為程序中的一個值。前者就被叫做序列化,而后者則被稱為反序列化。

換句話說,protocol buffers 定義和實現了一種“可以讓數據在結構形態和扁平形態之間互相轉換”的方式。

Protocol buffers 的優勢有不少。比如,它可以在序列化數據的同時對數據進行壓縮,所以它生成的字節流,通常都要比相同數據的其他格式(例如 XML 和 JSON)占用的空間明顯小很多。

又比如,它既能讓我們自己去定義數據序列化和結構化的格式,也允許我們在保證向后兼容的前提下去更新這種格式。

正因為這些優勢,Go 語言從 1.8 版本開始,把所有 profile 相關的信息生成工作都交給 protocol buffers 來做了。這也是我們在上述概要文件中,看不到普通文本的根本原因了。

Protocol buffers 的用途非常廣泛,並且在諸如數據存儲、數據傳輸等任務中有着很高的使用率。不過,關於它,我暫時就介紹到這里。你目前知道這些也就足夠了。你並不用關心runtime/pprof包以及runtime包中的程序是如何序列化這些概要信息的。

繼續回到怎樣生成概要文件的話題,我們依然通過具體的問題來講述。

我們今天的問題是:怎樣讓程序對 CPU 概要信息進行采樣?

這道題的典型回答是這樣的。

這需要用到runtime/pprof包中的 API。更具體地說,在我們想讓程序開始對 CPU 概要信息進行采樣的時候,需要調用這個代碼包中的StartCPUProfile函數,而在停止采樣的時候則需要調用該包中的StopCPUProfile函數。

問題解析

runtime/pprof.StartCPUProfile函數(以下簡稱StartCPUProfile函數)在被調用的時候,先會去設定 CPU 概要信息的采樣頻率,並會在單獨的 goroutine 中進行 CPU 概要信息的收集和輸出。

注意,StartCPUProfile函數設定的采樣頻率總是固定的,即:100赫茲。也就是說,每秒采樣100次,或者說每10毫秒采樣一次。

赫茲,也稱 Hz,是從英文單詞“Hertz”(一個英文姓氏)音譯過來的一個中文詞。它是 CPU 主頻的基本單位。

CPU 的主頻指的是,CPU 內核工作的時鍾頻率,也常被稱為 CPU clock speed。這個時鍾頻率的倒數即為時鍾周期(clock cycle),也就是一個 CPU 內核執行一條運算指令所需的時間,單位是秒。

例如,主頻為1000Hz 的 CPU,它的單個內核執行一條運算指令所需的時間為0.001秒,即1毫秒。又例如,我們現在常用的3.2GHz 的多核 CPU,其單個內核在1個納秒的時間里就可以至少執行三條運算指令。

StartCPUProfile函數設定的 CPU 概要信息采樣頻率,相對於現代的 CPU 主頻來說是非常低的。這主要有兩個方面的原因。

一方面,過高的采樣頻率會對 Go 程序的運行效率造成很明顯的負面影響。因此,runtime包中SetCPUProfileRate函數在被調用的時候,會保證采樣頻率不超過1MHz(兆赫),也就是說,它只允許每1微秒最多采樣一次。StartCPUProfile函數正是通過調用這個函數來設定 CPU 概要信息的采樣頻率的。

另一方面,經過大量的實驗,Go 語言團隊發現100Hz 是一個比較合適的設定。因為這樣做既可以得到足夠多、足夠有用的概要信息,又不至於讓程序的運行出現停滯。另外,操作系統對高頻采樣的處理能力也是有限的,一般情況下,超過500Hz 就很可能得不到及時的響應了。

在StartCPUProfile函數執行之后,一個新啟用的 goroutine 將會負責執行 CPU 概要信息的收集和輸出,直到runtime/pprof包中的StopCPUProfile函數被成功調用。

StopCPUProfile函數也會調用runtime.SetCPUProfileRate函數,並把參數值(也就是采樣頻率)設為0。這會讓針對 CPU 概要信息的采樣工作停止。

同時,它也會給負責收集 CPU 概要信息的代碼一個“信號”,以告知收集工作也需要停止了。

在接到這樣的“信號”之后,那部分程序將會把這段時間內收集到的所有 CPU 概要信息,全部寫入到我們在調用StartCPUProfile函數的時候指定的寫入器中。只有在上述操作全部完成之后,StopCPUProfile函數才會返回。

好了,經過這一番解釋,你應該已經對 CPU 概要信息的采樣工作有一定的認識了。你可以去看看 demo96.go 文件中的代碼,並運行幾次試試。這樣會有助於你加深對這個問題的理解。

總結

我們這兩篇內容講的是 Go 程序的性能分析,這其中的內容都是你從事這項任務必備的一些知識和技巧。

首先,我們需要知道,與程序性能分析有關的 API 主要存在於runtime、runtime/pprof和net/http/pprof這幾個代碼包中。它們可以幫助我們收集相應的性能概要信息,並把這些信息輸出到我們指定的地方。

Go 語言的運行時系統會根據要求對程序的相關指標進行多次采樣,並對采樣的結果進行組織和整理,最后形成一份完整的性能分析報告。這份報告就是我們一直在說的概要信息的匯總。

一般情況下,我們會把概要信息輸出到文件。根據概要信息的不同,概要文件的種類主要有三個,分別是:CPU 概要文件(CPU Profile)、內存概要文件(Mem Profile)和阻塞概要文件(Block Profile)。

在本文中,我提出了一道與上述幾種概要信息有關的問題。在下一篇文章中,我們會繼續對這部分問題的探究。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM