วิเคราะห์ประสิทธิภาพการผลิตด้วย 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.png

b9a10ebdf5b5a448.png

a1e3c01a38fa61c2.png

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

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

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

Google Cloud Shell

แม้ว่า Google Cloud จะใช้งานจากระยะไกลจากแล็ปท็อปได้ แต่เพื่อให้การตั้งค่าในโค้ดแล็บนี้ง่ายขึ้น เราจะใช้ 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. วิเคราะห์ข้อมูลโปรไฟล์

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

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

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

fae661c9fe6c58df.png

ฟังก์ชันนี้เขียนขึ้นมาโดยเฉพาะเพื่อใช้รอบ 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" ในชื่อ การกรองดังกล่าวมีประโยชน์เมื่อคุณต้องการมุ่งเน้นที่ส่วนย่อยของโปรแกรมที่ใหญ่ขึ้น (เช่น เนื่องจากคุณเป็นเจ้าของเพียงบางส่วน)

eb1d97491782b03f.png

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

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

f6311c8c841d04c4.png

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

ebd57fdff01dede9.png

ในข้อมูลสรุปเหนือกราฟเปลวไฟ คุณจะเห็นว่ามี 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 วินาที

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 แบบทั่วไป