วิเคราะห์ประสิทธิภาพการผลิตด้วย Cloud Profiler

1. ภาพรวม

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

95c034c70c9cac22.png

เครื่องมือจะรวบรวมข้อมูลการใช้งาน CPU และการจัดสรรหน่วยความจำจากแอปพลิเคชันที่ใช้งานจริง โดยจะเชื่อมโยงข้อมูลดังกล่าวกับซอร์สโค้ดของแอปพลิเคชัน ซึ่งจะช่วยให้คุณระบุส่วนของแอปพลิเคชันที่ใช้ทรัพยากรมากที่สุด และแสดงลักษณะด้านประสิทธิภาพของโค้ด เทคนิคการรวบรวมข้อมูลที่มีค่าใช้จ่ายต่ำของเครื่องมือนี้ทำให้เหมาะสำหรับการใช้งานอย่างต่อเนื่องในสภาพแวดล้อมการผลิต

ใน Codelab นี้ คุณจะได้เรียนรู้วิธีตั้งค่า Cloud Profiler สำหรับโปรแกรม Go และทำความคุ้นเคยกับข้อมูลเชิงลึกเกี่ยวกับประสิทธิภาพของแอปพลิเคชันที่เครื่องมือนี้แสดงได้

สิ่งที่คุณจะได้เรียนรู้

  • วิธีกำหนดค่าโปรแกรม Go สำหรับการทำโปรไฟล์ด้วย Cloud Profiler
  • วิธีกรอก ดู และวิเคราะห์ข้อมูลประสิทธิภาพด้วย Cloud Profiler

สิ่งที่คุณต้องมี

  • โปรเจ็กต์ Google Cloud Platform
  • เบราว์เซอร์ เช่น Chrome หรือ Firefox
  • มีความคุ้นเคยกับโปรแกรมแก้ไขข้อความมาตรฐานของ Linux เช่น Vim, EMAC หรือ Nano

คุณจะใช้บทแนะนำนี้อย่างไร

อ่านอย่างเดียว อ่านและทำแบบฝึกหัด

คุณจะให้คะแนนประสบการณ์การใช้งาน Google Cloud Platform เท่าใด

ผู้ฝึกหัด ขั้นกลาง ผู้ชำนาญ

2. การตั้งค่าและข้อกำหนด

การตั้งค่าสภาพแวดล้อมแบบเรียนรู้ด้วยตนเอง

  1. ลงชื่อเข้าใช้ Cloud Console แล้วสร้างโปรเจ็กต์ใหม่หรือใช้โปรเจ็กต์ที่มีอยู่ซ้ำ หากยังไม่มีบัญชี Gmail หรือ Google Workspace คุณต้องสร้างบัญชี

96a9c957bc475304.pngb9a10ebdf5b5a448.pnga1e3c01a38fa61c2.png

โปรดจดจำรหัสโปรเจ็กต์ ซึ่งเป็นชื่อที่ไม่ซ้ำกันในโปรเจ็กต์ Google Cloud ทั้งหมด (ชื่อด้านบนมีผู้ใช้แล้วและจะใช้ไม่ได้ ขออภัย) ซึ่งจะเรียกว่า PROJECT_ID ในภายหลังใน Codelab นี้

  1. จากนั้นคุณจะต้องเปิดใช้การเรียกเก็บเงินใน Cloud Console เพื่อใช้ทรัพยากร Google Cloud

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

Google Cloud Shell

แม้ว่าคุณจะใช้งาน Google Cloud จากแล็ปท็อประยะไกลได้ แต่ใน Codelab นี้เราจะใช้ Google Cloud Shell ซึ่งเป็นสภาพแวดล้อมบรรทัดคำสั่งที่ทำงานในระบบคลาวด์เพื่อให้การตั้งค่าทำได้ง่ายขึ้น

เปิดใช้งาน Cloud Shell

  1. จาก Cloud Console ให้คลิกเปิดใช้งาน Cloud Shell 4292cbf4971c9786.png

bce75f34b2c53987.png

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

70f315d7b402b476.png

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

fbe3a0674c982259.png

เครื่องเสมือนนี้มีเครื่องมือพัฒนาซอฟต์แวร์ทั้งหมดที่คุณต้องการ โดยมีไดเรกทอรีหลักขนาด 5 GB ที่คงอยู่ถาวรและทำงานใน Google Cloud ซึ่งช่วยเพิ่มประสิทธิภาพเครือข่ายและการตรวจสอบสิทธิ์ได้อย่างมาก คุณสามารถทำงานในโค้ดแล็บนี้ได้โดยใช้เพียงเบราว์เซอร์หรือ Chromebook

เมื่อเชื่อมต่อกับ Cloud Shell แล้ว คุณควรจะเห็นว่าคุณได้รับการตรวจสอบสิทธิ์แล้วและโปรเจ็กต์ได้รับการตั้งค่าเป็นรหัสโปรเจ็กต์ของคุณแล้ว

  1. เรียกใช้คำสั่งต่อไปนี้ใน 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`
  1. เรียกใช้คำสั่งต่อไปนี้ใน 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" ในแถบนำทางด้านซ้าย

37ad0df7ddb2ad17.png

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

d275a5f61ed31fb2.png

ตอนนี้ได้เวลาสร้างโปรไฟล์แล้ว

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 ครั้ง

650051097b651b91.png

หลังจาก UI รีเฟรชแล้ว คุณจะเห็นข้อความคล้ายกับข้อความต่อไปนี้

47a763d4dc78b6e8.png

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

b5d7b4b5051687c9.png

ตอนนี้มาดูโปรไฟล์แต่ละประเภทและความสามารถที่สำคัญบางอย่างของ UI แล้วทำการทดสอบกัน ในขั้นตอนนี้ คุณไม่จำเป็นต้องใช้เทอร์มินัล Cloud Shell อีกต่อไป จึงออกได้โดยกด CTRL-C แล้วพิมพ์ "exit"

5. วิเคราะห์ข้อมูล Profiler

ตอนนี้เราได้รวบรวมข้อมูลบางส่วนแล้ว มาดูรายละเอียดกัน เราใช้แอปสังเคราะห์ (แหล่งที่มามีอยู่ใน GitHub) ที่จำลองลักษณะการทำงานซึ่งมักพบในปัญหาด้านประสิทธิภาพประเภทต่างๆ ในเวอร์ชันที่ใช้งานจริง

โค้ดที่ใช้ CPU สูง

เลือกประเภทโปรไฟล์ CPU หลังจาก UI โหลดแล้ว คุณจะเห็นบล็อก 4 ใบสำหรับฟังก์ชัน load ในกราฟ Flame ซึ่งรวมกันแล้วคิดเป็นปริมาณการใช้ CPU ทั้งหมด

fae661c9fe6c58df.png

ฟังก์ชันนี้เขียนขึ้นมาโดยเฉพาะเพื่อใช้รอบ CPU จำนวนมากด้วยการเรียกใช้ลูปที่เข้มงวด

main.go

func load() {
        for i := 0; i < (1 << 20); i++ {
        }
}

ฟังก์ชันนี้เรียกใช้โดยอ้อมจาก busyloop() ผ่านเส้นทางการเรียกใช้ 4 เส้นทาง ได้แก่ busyloop → {foo1, foo2} → {bar, baz} → load ความกว้างของกล่องฟังก์ชันแสดงถึงต้นทุนโดยเทียบเคียงของเส้นทางการเรียกที่เฉพาะเจาะจง ในกรณีนี้ เส้นทางทั้ง 4 เส้นทางมีต้นทุนใกล้เคียงกัน ในโปรแกรมจริง คุณควรเน้นการเพิ่มประสิทธิภาพเส้นทางการโทรที่มีความสำคัญมากที่สุดในแง่ของประสิทธิภาพ กราฟ Flame ซึ่งเน้นเส้นทางที่มีค่าใช้จ่ายสูงกว่าด้วยกล่องที่ใหญ่กว่าจะช่วยให้ระบุเส้นทางเหล่านี้ได้ง่าย

คุณสามารถใช้ตัวกรองข้อมูลโปรไฟล์เพื่อปรับแต่งการแสดงผลเพิ่มเติมได้ เช่น ลองเพิ่มตัวกรอง "แสดงสแต็ก" โดยระบุ "baz" เป็นสตริงตัวกรอง คุณควรเห็นลักษณะคล้ายกับภาพหน้าจอด้านล่าง ซึ่งแสดงเส้นทางการโทรไปยัง load() เพียง 2 เส้นทางจากทั้งหมด 4 เส้นทาง เส้นทางทั้ง 2 นี้เป็นเส้นทางเดียวที่ผ่านฟังก์ชันที่มีสตริง "baz" ในชื่อ การกรองดังกล่าวมีประโยชน์เมื่อคุณต้องการมุ่งเน้นที่ส่วนย่อยของโปรแกรมที่ใหญ่ขึ้น (เช่น เนื่องจากคุณเป็นเจ้าของเพียงบางส่วน)

eb1d97491782b03f.png

โค้ดที่ใช้หน่วยความจำสูง

ตอนนี้ให้เปลี่ยนไปใช้ประเภทโปรไฟล์ "Heap" โปรดนำตัวกรองที่คุณสร้างในการทดสอบก่อนหน้านี้ออก ตอนนี้คุณควรเห็นกราฟ Flame ที่ allocImpl ซึ่งเรียกใช้โดย alloc แสดงเป็นผู้ใช้หน่วยความจำหลักในแอป

f6311c8c841d04c4.png

ตารางสรุปเหนือกราฟ Flame ระบุว่าปริมาณหน่วยความจำที่ใช้ทั้งหมดในแอปโดยเฉลี่ยอยู่ที่ประมาณ 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" ในตัวเลือกประเภทโปรไฟล์ จอแสดงผลจะเปลี่ยนเป็นกราฟ Flame ซึ่งฟังก์ชัน wait และ waitImpl จะใช้พื้นที่ส่วนใหญ่

ebd57fdff01dede9.png

ในข้อมูลสรุปเหนือกราฟ Flame คุณจะเห็นว่ามี 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" ในโค้ดการกำหนดค่า Agent การรวบรวมจะบันทึก (ภายใต้เมตริก "ข้อโต้แย้ง") จำนวนครั้งที่ล็อกหนึ่งๆ เมื่อ Goroutine A ปลดล็อกแล้ว จะมี Goroutine B อีกตัวรอให้ปลดล็อก นอกจากนี้ยังบันทึกเวลาที่ Goroutine ที่ถูกบล็อกรอการล็อก (ในเมตริก "Delay") ด้วย ในตัวอย่างนี้ มีสแต็กการแย่งชิงรายการเดียวและเวลารอทั้งหมดสำหรับล็อกคือ 10.5 วินาที

83f00dca4a0f768e.png

โค้ดที่สร้างโปรไฟล์นี้ประกอบด้วย 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 แล้ว

ดูข้อมูลเพิ่มเติม

ใบอนุญาต

ผลงานนี้ได้รับอนุญาตภายใต้สัญญาอนุญาตครีเอทีฟคอมมอนส์สำหรับยอมรับสิทธิของผู้สร้าง (Creative Commons Attribution License) 2.0 แบบทั่วไป