1. ภาพรวม
แม้ว่านักพัฒนาแอปไคลเอ็นต์และนักพัฒนาเว็บส่วนหน้ามักจะใช้เครื่องมือต่างๆ เช่น Android Studio CPU Profiler หรือเครื่องมือการสร้างโปรไ��ล์ที่รวมอยู่ใน Chrome เพื่อปรับปรุงประสิทธิภาพของโค้ด แต่เทคนิคที่เทียบเท่ากันนั้นยังไม่สามารถเข้าถึงได้ง่ายหรือเป็นที่นิยมในหมู่ผู้ที่ทำงานกับบริการแบ็กเอนด์ Cloud Profiler นำความสามารถเดียวกันนี้มาสู่ผู้พัฒนาบริการ ไม่ว่าโค้ดจะทำงานบน Google Cloud Platform หรือที่อื่นก็ตาม

เครื่องมือจะรวบรวมข้อมูลการใช้งาน CPU และการจัดสรรหน่วยความจำจากแอปพลิเคชันที่ใช้งานจริง โดยจะระบุข้อมูลดังกล่าวกับซอร์สโค้ดของแอปพลิเคชัน ซึ่งจะช่วยให้คุณระบุส่วนของแอปพลิเคชันที่ใช้ทรัพยากรมากที่สุด และแสดงลักษณะด้านประสิทธิภาพของโค้ด เทคนิคการรวบรวมข้อมูลที่มีค่าใช้จ่ายต่ำของเครื่องมือนี้ทำให้เหมาะสำหรับการใช้งานอย่างต่อเนื่องในสภาพแวดล้อมการผลิต
ใน Codelab นี้ คุณจะได้เรียนรู้วิธีตั้งค่า Cloud Profiler สำหรับโปรแกรม Go และทำความคุ้นเคยกับข้อมูลเชิงลึกเกี่ยวกับประสิทธิภาพของแอปพลิเคชันที่เครื่องมือนี้แสดงได้
สิ่งที่คุณจะได้เรียนรู้
- วิธีกำหนดค่าโปรแกรม Go สำหรับการทำโปรไฟล์ด้วย Cloud Profiler
- วิธีรวบรวม ดู และวิเคราะห์ข้อมูลประสิทธิภาพด้วย Cloud Profiler
สิ่งที่คุณต้องมี
- โปรเจ็กต์ Google Cloud Platform
- เบราว์เซอร์ เช่น Chrome หรือ Firefox
- คุ้นเคยกับโปรแกรมแก้ไขข้อความมาตรฐานของ Linux เช่น Vim, EMAC หรือ Nano
คุณจะใช้บทแนะนำนี้อย่างไร
คุณจะให้คะแนนประสบการณ์การใช้งาน Google Cloud Platform เท่าไร
2. การตั้งค่าและข้อกำหนด
การตั้งค่าสภาพแวดล้อมแบบเรียนรู้ด้วยตนเอง
- ลงชื่อเข้าใช้ Cloud Console แล้วสร้างโปรเจ็กต์ใหม่หรือใช้โปรเจ็กต์ที่มีอยู่ซ้ำ หากยังไม่มีบัญชี Gmail หรือ Google Workspace คุณต้องสร้างบัญชี



โปรดจดจำรหัสโปรเจ็กต์ ซึ่งเป็นชื่อที่ไม่ซ้ำกันในโปรเจ็กต์ Google Cloud ทั้งหมด (ชื่อด้านบนมีผู้ใช้แล้วและจะใช้ไม่ได้ ขออภัย) ซึ่งจะเรียกว่า PROJECT_ID ในภายหลังใน Codelab นี้
- จากนั้น��ุณจะต้องเปิดใช้การเรียกเก็บเงินใน Cloud Console เพื่อใช้ทรัพยากร Google Cloud
การทำตาม Codelab นี้ไม่ควรมีค่าใช้จ่ายมากนัก หรืออาจไม่มีเลย โปรดทำตามวิธีการในส่วน "การล้างข้อมูล" ซึ่งจะแนะนำวิธีปิดทรัพยากรเพื่อไม่ให้มีการเรียกเก็บเงินนอกเหนือจากบทแนะนำนี้ ผู้ใช้ Google Cloud รายใหม่มีสิทธิ์เข้าร่วมโปรแกรมช่วงทดลองใช้ฟรีมูลค่า$300 USD
Google Cloud Shell
แม้ว่า Google Cloud จะใช้งานจากระยะไกลจากแล็ปท็อปได้ แต่เพื่อให้การตั้งค่าในโค้ดแล็บนี้ง่ายขึ้น เราจะใช้ Google Cloud Shell ซึ่งเป็นสภาพแวดล้อมบรรทัดคำสั่งที่ทำงานในระบบคลาวด์
เปิดใช้งาน Cloud Shell
- จาก Cloud Console ให้คลิกเปิดใช้งาน Cloud Shell


หากไม่เคยเริ่มใช้ Cloud Shell มาก่อน คุณจะเห็นหน้าจอระดับกลาง (ด้านล่าง) ที่อธิบายว่า Cloud Shell คืออะไร ในกรณีนี้ ให้คลิกต่อไป (และคุณจะไม่เห็นหน้าจอนี้อีก) หน้าจอแบบครั้งเดียวจะมีลักษณะดังนี้

การจัดสรรและเชื่อมต่อกับ Cloud Shell จะใช้เวลาไม่นาน

เครื่องเสมือนนี้มีเครื่องมือพัฒนาซอฟต์แวร์ทั้งหมดที่��ุณต้องการ โดยมีไดเรกทอรีหลักแบบถาวรขนาด 5 GB และทำงานใน Google Cloud ซึ่งช่วยเพิ่มประสิทธิภาพเครือข่ายและการตรวจสอบสิทธิ์ได้อย่างมาก คุณสามารถทำงานในโค้ดแล็บนี้ได้โดยใช้เพียงเบราว์เซอร์หรือ Chromebook
เมื่อเชื่อมต่อกับ Cloud Shell แล้ว คุณควรเห็นว่าคุณได้รับการตรวจสอบสิทธิ์แล้วและโปรเจ็กต์ได้รับการตั้งค่าเป็นรหัสโปรเจ็กต์ของคุณแล้ว
- เรียกใช้คำสั่งต่อไปนี้ใน 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 Console ให้ไปที่ UI ของ Profiler โดยคลิก "Profiler" ในแถบนำทางด้านซ้าย

หรือคุณจะใช้แถบค้นหาของ Cloud Console เพื่อไปยัง UI ของ Profiler ก็ได้ เพียงพิมพ์ "Cloud Profiler" แล้วเลือกรายการที่พบ ไม่ว่าจะด้วยวิธีใด คุณควรเห็น UI ของเครื่องมือสร้างโปรไฟล์พร้อมข้อความ "ไม่มีข้อมูลที่จะแส��ง" ดังที่แสดงด้านล่าง โปรเจ็กต์นี้เป็นโปรเจ็กต์ใหม่ จึงยังไม่มีข้อมูลการจัดโปรไฟล์ที่รวบรวมไว้

ตอนนี้ถึงเวลาสร้างโปรไฟล์แล้ว
4. สร้างโปรไฟล์การเปรียบเทียบ
เราจะใช้แอปพลิเคชัน Go แบบสังเคราะห์อย่างง่ายซึ่งมีอยู่ใน Github ในเทอร์มินัล Cloud Shell ที่คุณยังเปิดอยู่ (และขณะที่ข้อความ "ไม่มีข้อมูลที่จะแสดง" ยังคงแสดงใน UI ของ Profiler) ให้เรียกใช้คำสั่งต่อไปนี้
$ 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, ฮีป และเธรด โค้ดที่นี่ช่วยให้รวบรวมโปรไฟล์ Mutex (หรือที่เรียกว่า "การแย่งชิง") ได้
ตอนนี้ให้เรียกใช้โปรแกรมโดยทำดังนี้
$ go run main.go
ขณะที่โปรแกรมทำงาน เอเจนต์การสร้างโปรไฟล์จะรวบรวมโปรไฟล์ของประเภทที่กำหนดค่าไว้ 5 ประเภทเป็นระยะๆ ระบบจะสุ่มรวบรวมข้อมูลเมื่อเวลาผ่านไป (โดยมีอัตราเฉลี่ย 1 โปรไฟล์ต่อนาทีสำหรับแต่ละประเภท) ดังนั้นอาจใช้เวลาถึง 3 นาทีในการรวบรวมข้อมูลแต่ละประเภท โปรแกรมจะแจ้งให้คุณทราบเมื่อสร้างโปรไฟล์ ข้อความจะเปิดใช้โดยแฟล็ก DebugLogging ในการกำหนดค่าด้านบน มิฉะนั้น Agent จะทำงานโดยไม่มีการแจ้งเตือน
$ 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 ของ Profiler ด้วยตนเอง โดยคลิกปุ่ม "ตอนนี้" ในตัวเลือกช่วงเวลา 2 ครั้ง

หลังจาก UI รีเฟรชแล้ว คุณจะเห็นข้อมูลในลักษณะนี้

ตัวเลือกประเภทโปรไฟล์จะแสดงโปรไฟล์ 5 ประเภทที่พร้อมใช้งาน ดังนี้

ตอนนี้มาดูโปรไฟล์แต่ละประเภทและความสามารถที่สำค��ญบางอย่างของ UI แล้วทำการทดสอบกัน ในขั้นตอนนี้ คุณไม่จำเป็นต้องใช้เทอร์มินัล Cloud Shell อีกต่อไป จึงออกได้โดยกด CTRL-C แล้วพิมพ์ "exit"
5. วิเคราะห์ข้อมูลโปรไฟล์
ตอนนี้เราได้รวบรวมข้อมูลบางส่วนแล้ว มาดูรายละเอียดกัน เราใช้แอปสังเคราะห์ (แหล่งที่มามีอยู่ใน GitHub) ซึ่งจำลองลักษณะการทำงานที่มักพบในปัญหาด้านประสิทธิภาพประเภทต่างๆ ในเวอร์ชันที่ใช้งานจริง
โค้ดที่ใช้ CPU สูง
เลือกประเภทโปรไฟล์ CPU หลังจาก UI โหลดแล้ว คุณจ��เ��็น��������ก 4 ใบสำหรับฟังก์ชั�� load ในกราฟเปลวไฟ ซึ่งรวมกันแล้วคิดเป็นปริมาณการใช้ CPU ทั้งหมด

ฟังก์ชันนี้เขียนขึ้นมาโดยเฉพาะเพื่อใช้รอบ CPU จำนวนมากโดยการเรียกใช้ลูปที่เข้มงวด
main.go
func load() {
for i := 0; i < (1 << 20); i++ {
}
}
ฟังก์ชันนี้เรียกใช้โดยอ้อมจาก busyloop() ผ่านเส้นทางการเรียกใช้ 4 เส้นทาง ได้แก่ busyloop → {foo1, foo2} → {bar, baz} → load ความกว้างของกล่องฟังก์ชันแสดงถึงต้นทุนโดยเทียบเคียงของเส้นทางการเรียกที่เฉพาะเจาะจง ในกรณีนี้ เส้นทางทั้ง 4 เส้นทางมีต้นทุนใกล้เคียงกัน ในโปรแกรมจริง คุณควรเน้นการเพิ่มประสิทธิภาพเส้นทางการโทรที่มีความสำคัญมากที่สุดในแง่ของประสิทธิภาพ กราฟเปลวไฟซึ่งเน้นเส้นทางที่มีค่าใช้จ่ายสูงกว่าด้วยกล่องที่ใหญ่กว่าจะช่วยให้ระบุเส้นทางเหล่านี้ได้ง่าย
คุณสามารถใช้ตัวกรองข้อมูลโปรไฟล์เพื่อปรับแต่งการแสดงผลเพิ่มเติมได้ เช่น ลองเพิ่มตัวกรอง "แสดงสแต็ก" โดยระบุ "baz" เป็นสตริงตัวกรอง คุณควรเห็นสิ่งที่คล้ายกับภาพหน้าจอด้านล่าง ซึ่งแสดงเส้นทางการโทรไปยัง load() เพียง 2 เส้นทางจากทั้งหมด 4 เส้นทาง เส้นทาง 2 เส้นทางนี้เป็นเส้นทางเดียวที่ผ่านฟังก์ชันที่มีสตริง "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% เช่นนี้
โค้ดที่ใช้ I/O จำนวนมาก
หากเลือก "Threads" ในตัวเลือกประเภทโปรไฟล์ จอแ��ดงผลจะเปลี่ยนเป็นกราฟเปลวไฟซึ่งฟังก์ชัน wait และ waitImpl จะใช้พื้นที่ส่วนใหญ่

ในข้อมูลสรุปเหนือกราฟเปลวไฟ คุณจะเห็นว่ามี Goroutine 100 รายการที่ขยายสแต็กการเรียกจากฟังก์ชัน wait ซึ่งถูกต้องแล้ว เนื่องจากโค้ดที่เริ่มต้นการรอเหล่านี้มีลักษณะดังนี้
main.go
func main() {
...
// Simulate some waiting goroutines.
for i := 0; i < 100; i++ {
go wait()
}
ประเภทโปรไฟล์นี้มีประโยชน์ในการทำความเข้าใจว่าโปรแกรมใช้เวลาในการรอ (เช่น I/O) โดยไม่คาดคิดหรือไม่ โดยปกติแล้วโปรไฟล์เลอร์ CPU จะไม่สุ่มตัวอย่างสแต็กการเรียกดังกล่าว เนื่องจากไม่ได้ใช้เวลา CPU เป็นสัดส่วนที่สำคัญ คุณมักต้องการใช้ตัวกรอง "ซ่อนสแต็ก" กับโปรไฟล์ Threads เช่น เพื่อซ่อนสแต็กทั้งหมดที่ลงท้ายด้วยการเรียกใช้ gopark, เนื่องจากมักจะเป็น Goroutine ที่ไม่ได้ใช้งานและน่าสนใจน้อยกว่า Goroutine ที่รอ I/O
ประเภทโปรไฟล์ของเธรดอาจช่วยระบุจุดในโปรแกรมที่เธรดรอ Mutex ที่เป็นของส่วนอื่นของโปรแกรมเป็นเวลานานได้ด้วย แต่ประเภทโปรไฟล์ต่อไปนี้มีประโยชน์มากกว่าในกรณีดังกล่าว
โค้ดที่มีการแย่งชิงสูง
ประเภทโปรไฟล์การแย่งชิงจะระบุการล็อกที่ "ต้องการ" มากที่������ดในโปรแกรม โปรไฟล์ประเภทนี้ใช้ได้กับโปรแกรม Go แต่ต้องเปิดใช้โดยชัดแจ้งด้วยการระบุ "MutexProfiling: true" ในโค้ดการกำหนดค่าเอเจนต์ การรวบรวมจะทำงานโดยการบันทึก (ภายใต้เมตริก "การโต้แย้ง") จำนวนครั้งที่การล็อกที่เฉพาะเจาะจง เมื่อ Goroutine A ปลดล็อก จะมี Goroutine B อีกตัวรอให้ปลดล็อก นอกจากนี้ยังบันทึกเวลาที่ Goroutine ที่ถูกบล็อกรอการล็อก (ภายใต้เมตริก "Delay") ด้วย ในตัวอย่างนี้ มีสแต็กการแย่งชิงรายการเดียวและเวลารอทั้งหมดสำหรับการล็อกคือ 10.5 วินาที

โค้ดที่สร้างโปรไฟล์นี้ประกอบด้วย Goroutine 4 รายการที่แย่งกันใช้ Mutex
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/
- แพ็กเกจ runtime/pprof ที่ Cloud Profiler ใช้: https://golang.org/pkg/runtime/pprof/
ใบอนุญาต
ผลงานนี้ได้รับอนุญาตภายใต้สัญญาอนุญาตครีเอทีฟคอมมอนส์สำหรับยอมรับสิทธิของผู้สร้าง (Creative Commons Attribution License) 2.0 แบบทั่วไป