1. 總覽
用戶端應用程式和前端網頁開發人員通常會使用 Android Studio CPU 分析器或 Chrome 內建的分析工具等工具,改善程式碼的效能,但後端服務開發人員卻難以取得或採用同等技術。無論服務開發人員的程式碼是在 Google Cloud Platform 或其他位置執行,Cloud Profiler 都能提供相同的功能。

這項工具會從正式環境中的應用程式收集 CPU 用量和記憶體配置資訊。且可將收集到資訊進行應用程式原始碼屬性歸類,協助您識別使用最多資源的應用程式部分或者程式碼的效能特徵。這項工具採用的收集技術負擔較低,因此適合在正式環境中持續使用。
在本程式碼研究室中,您將瞭解如何為 Go 程式設定 Cloud Profiler,並熟悉這項工具可提供的應用程式效能洞察資訊。
課程內容
- 如何設定 Go 程式,以便使用 Cloud Profiler 進行剖析。
- 如何使用 Cloud Profiler 收集、查看及分析效能資料。
軟硬體需求
您會如何使用這項教學課程?
你對 Google Cloud Platform 的體驗滿意嗎?
2. 設定和需求
自修實驗室環境設定



請記住專案 ID,這是所有 Google Cloud 專案中不重複的名稱 (上述名稱已遭占用,因此不適用於您,抱歉!)。本程式碼研究室稍後會將其稱為 PROJECT_ID。
- 接著,您必須在 Cloud 控制台中啟用帳單,才能使用 Google Cloud 資源。
完成本程式碼研究室的費用應該不高,甚至完全免費。請務必按照「清除」部分的指示操作,瞭解如何停用資源,避免在本教學課程結束後繼續產生帳單費用。Google Cloud 新使用者可參加價值$300 美元的免費試用計畫。
Google Cloud Shell
雖然可以透過筆電遠端操作 Google Cloud,但為了簡化本程式碼研究室的設定,我們將使用 Google Cloud Shell,這是可在雲端執行的指令列環境。
啟用 Cloud Shell
- 在 Cloud 控制台,點選「啟用 Cloud Shell」 圖示
。

如果您是首次啟動 Cloud Shell,系統會顯示中繼畫面 (位於摺疊式選單下方),說明這個指令列環境。點選「繼續」後,這則訊息日後就不會再出現。以下是這個初次畫面的樣子:

佈建並連至 Cloud Shell 預計只需要幾分鐘。

這部虛擬機器搭載您需要的所有開發工具,並提供永久的 5GB 主目錄,而且可在 Google Cloud 運作,大幅提升網路效能並強化驗證功能。本程式碼研究室幾乎所有工作都可在瀏覽器或 Chromebook 上完成。
連線至 Cloud Shell 後,您應會發現自己通過驗證,且專案已設為您的專案 ID。
- 在 Cloud Shell 中執行下列指令,確認您已通過驗證:
gcloud auth list
指令輸出
Credentialed Accounts
ACTIVE ACCOUNT
* <my_account>@<my_domain.com>
To set the active account, run:
$ gcloud config set account `ACCOUNT`
- 在 Cloud Shell 中執行下列指令,確認 gcloud 指令知道您的專案:
gcloud config list project
指令輸出
[core] project = <PROJECT_ID>
如未設定,請輸入下列指令手動設定專案:
gcloud config set project <PROJECT_ID>
指令輸出
Updated property [core/project].
3. 前往 Cloud Profiler
在 Cloud 控制台中,按一下左側導覽列中的「Profiler」,前往 Profiler UI:

或者,您也可以使用 Cloud 控制台搜尋列前往 Profiler UI:只要輸入「Cloud Profiler」,然後選取找到的項目即可。無論哪種方式,您都應該會看到「沒有可顯示的資料」訊息,如下所示。這個專案是新專案,因此目前沒有任何收集到的剖析資料。

現在該來剖析一些內容了!
4. 剖析基準測試
我們將使用 Github 上提供的簡單合成 Go 應用程式。在您仍開啟的 Cloud Shell 終端機中 (且當「沒有資料可顯示」訊息仍顯示在分析器 UI 中時),執行下列指令:
$ go get -u github.com/GoogleCloudPlatform/golang-samples/profiler/...
然後切換至應用程式目錄:
$ cd ~/gopath/src/github.com/GoogleCloudPlatform/golang-samples/profiler/hotapp
這個目錄包含「main.go」檔案,這是啟用剖析代理程式的合成應用程式:
main.go
...
import (
...
"cloud.google.com/go/profiler"
)
...
func main() {
err := profiler.Start(profiler.Config{
Service: "hotapp-service",
DebugLogging: true,
MutexProfiling: true,
})
if err != nil {
log.Fatalf("failed to start the profiler: %v", err)
}
...
}
剖析代理程式預設會收集 CPU、堆積和執行緒剖析資料。這裡的程式碼可啟用互斥 (也稱為「爭用情況」) 剖析資料的收集作業。
現在執行程式:
$ go run main.go
程式執行時,剖析代理程式會定期收集五種設定類型的剖析資料。系統會隨機收集資料 (每種資料平均每分鐘收集一次),因此可能需要最多三分鐘才能收集到每種資料。程式會在建立設定檔時通知您。上述設定中的 DebugLogging 旗標會啟用訊息,否則代理程式會以無聲模式執行:
$ go run main.go 2018/03/28 15:10:24 profiler has started 2018/03/28 15:10:57 successfully created profile THREADS 2018/03/28 15:10:57 start uploading profile 2018/03/28 15:11:19 successfully created profile CONTENTION 2018/03/28 15:11:30 start uploading profile 2018/03/28 15:11:40 successfully created profile CPU 2018/03/28 15:11:51 start uploading profile 2018/03/28 15:11:53 successfully created profile CONTENTION 2018/03/28 15:12:03 start uploading profile 2018/03/28 15:12:04 successfully created profile HEAP 2018/03/28 15:12:04 start uploading profile 2018/03/28 15:12:04 successfully created profile THREADS 2018/03/28 15:12:04 start uploading profile 2018/03/28 15:12:25 successfully created profile HEAP 2018/03/28 15:12:25 start uploading profile 2018/03/28 15:12:37 successfully created profile CPU ...
收集到第一個設定檔後,使用者介面很快就會更新。之後系統不會自動更新,因此如要查看新資料,您必須手動重新整理分析器使用者介面。如要這麼做,請在時間間隔挑選器中按兩下「立即」按鈕:

UI 重新整理後,您會看到類似下方的內容:

設定檔類型選取器會顯示五種可用的設定檔類型:

現在,我們來逐一瞭解各類設定檔和一些重要的 UI 功能,然後進行一些實驗。此時您不再需要 Cloud Shell 終端機,因此可以按下 Ctrl-C 並輸入「exit」來退出。
5. 分析分析器資料
收集到一些資料後,現在就來仔細查看。我們使用合成應用程式 (Github 上提供來源),模擬實際工作環境中各種效能問題的典型行為。
耗用大量 CPU 的程式碼
選取 CPU 設定檔類型。載入 UI 後,您會在火焰圖中看到 load 函式的四個葉塊,這些葉塊共同代表所有 CPU 耗用量:

這個函式專門用於執行緊密迴圈,藉此耗用大量 CPU 週期:
main.go
func load() {
for i := 0; i < (1 << 20); i++ {
}
}
函式是透過四個呼叫路徑,從 busyloop() 間接呼叫:busyloop → {foo1, foo2} → {bar, baz} → load。函式方塊的寬度代表特定呼叫路徑的相對費用。在這個例子中,四條路徑的費用大致相同。在實際程式中,您會想著重於最佳化對效能影響最大的呼叫路徑。火焰圖會以較大的方塊,在視覺上強調較昂貴的路徑,方便您找出這些路徑。
您可以使用設定檔資料篩選器,進一步調整顯示內容。舉例來說,請嘗試新增「顯示堆疊」篩選器,並將「baz」指定為篩選字串。您應該會看到類似下方的螢幕截圖,其中只會顯示四個 load() 呼叫路徑中的兩個。這兩條路徑是唯一會經過名稱中含有「baz」字串的函式。如果您只想專注於大型節目的一小部分 (例如您只擁有部分內容),這類篩選功能就非常實用。

耗用大量記憶體的程式碼
現在切換至「Heap」設定檔類型。請務必移除先前實驗中建立的所有篩選器。您現在應該會看到火焰圖,其中 allocImpl (由 alloc 呼叫) 會顯示為應用程式的主要記憶體消費者:

火焰圖上方的摘要表格指出,應用程式使用的記憶體總量平均約為 57.4 MiB,其中大部分是由 allocImpl 函式分配。鑑於此函式的實作方式,這並不令人意外:
main.go
func allocImpl() {
// Allocate 64 MiB in 64 KiB chunks
for i := 0; i < 64*16; i++ {
mem = append(mem, make([]byte, 64*1024))
}
}
函式會執行一次,以較小的區塊分配 64 MiB,然後將這些區塊的指標儲存在全域變數中,避免遭到垃圾收集。請注意,分析器顯示的記憶體用量與 64 MiB 略有不同:Go 堆積分析器是統計工具,因此測量結果的負擔較低,但並非以位元組為單位。因此看到約 10% 的差異時,請不要感到意外。
IO 密集型程式碼
如果在設定檔類型選取器中選擇「執行緒」,顯示畫面會切換為火焰圖,其中大部分的寬度會由 wait 和 waitImpl 函式佔用:

在火焰圖上方的摘要中,您可以看到有 100 個 goroutine 從 wait 函式擴展呼叫堆疊。這完全正確,因為啟動這些等待的程式碼如下所示:
main.go
func main() {
...
// Simulate some waiting goroutines.
for i := 0; i < 100; i++ {
go wait()
}
這類設定檔有助於瞭解程式是否在等待 (例如 I/O) 時耗費非預期時間。這類呼叫堆疊通常不會由 CPU 分析器取樣,因為不會消耗大量 CPU 時間。您通常會想搭配使用「隱藏堆疊」篩選器和執行緒設定檔,例如隱藏所有以呼叫 gopark, 結尾的堆疊,因為這些通常是閒置的 Goroutine,相較於等待 I/O 的 Goroutine,較不值得關注。
執行緒剖析類型也有助於找出程式中執行緒等待程式其他部分擁有的互斥鎖時間過長的位置,但下列剖析類型更適合用於此用途。
競爭密集型程式碼
「爭用」設定檔類型會找出程式中最「需要」的鎖定。這個剖析資料類型適用於 Go 程式,但必須在代理程式設定程式碼中指定「MutexProfiling: true」,才能明確啟用。這項收集作業會記錄 (在「爭用」指標下),特定鎖定由 goroutine A 解除鎖定時,另一個 goroutine B 等待鎖定解除鎖定的次數。此外,系統也會記錄遭封鎖的 Goroutine 等待鎖定的時間 (在「延遲」指標下)。在本例中,只有一個爭用堆疊,鎖定的總等待時間為 10.5 秒:

產生這個設定檔的程式碼包含 4 個爭奪互斥鎖的 Goroutine:
main.go
func contention(d time.Duration) {
contentionImpl(d)
}
func contentionImpl(d time.Duration) {
for {
mu.Lock()
time.Sleep(d)
mu.Unlock()
}
}
...
func main() {
...
for i := 0; i < 4; i++ {
go contention(time.Duration(i) * 50 * time.Millisecond)
}
}
6. 摘要
在本實驗室中,您瞭解如何設定 Go 程式,以便搭配 Cloud Profiler 使用。您也瞭解如何使用這項工具收集、查看及分析成效資料。您現在可以將新學到的技能,應用於 Google Cloud Platform 上執行的實際服務。
7. 恭喜!
您已學會如何設定及使用 Cloud Profiler!
瞭解詳情
- Cloud Profiler:https://cloud.google.com/profiler/
- Cloud Profiler 使用的 Go 執行階段/pprof 套件:https://golang.org/pkg/runtime/pprof/
授權
這項內容採用的授權為 Creative Commons 姓名標示 2.0 通用授權。