ข้อมูลเบื้องต้นเกี่ยวกับการเพิ่มประสิทธิภาพใน Go
Go หรือ Golang เป็นภาษาโปรแกรมโอเพ่นซอร์สสมัยใหม่ที่พัฒนาโดย Google โดย Robert Griesemer, Rob Pike และ Ken Thompson Go มีคุณสมบัติด้านประสิทธิภาพที่ยอดเยี่ยม ต้องขอบคุณความเรียบง่าย การพิมพ์ที่แข็งแกร่ง การสนับสนุน การทำงาน พร้อมกันในตัว และการรวบรวมขยะ นักพัฒนาเลือก Go เนื่องจากความเร็ว ความสามารถในการปรับขนาด และความง่ายในการบำรุงรักษาเมื่อสร้างแอปพลิเคชันฝั่งเซิร์ฟเวอร์ ไปป์ไลน์ข้อมูล และระบบประสิทธิภาพสูงอื่นๆ
หากต้องการบีบประสิทธิภาพสูงสุดจากแอปพลิเคชัน Go คุณอาจต้องการเพิ่มประสิทธิภาพโค้ดของคุณ สิ่งนี้จำเป็นต้องเข้าใจคอขวดของประสิทธิภาพ จัดการการจัดสรรหน่วยความจำอย่างมีประสิทธิภาพ และใช้ประโยชน์จากการทำงานพร้อมกัน กรณีสำคัญประการหนึ่งของการใช้ Go ในแอปพลิเคชันที่คำนึงถึงประสิทธิภาพคือ AppMaster ซึ่งเป็นแพลตฟอร์ม ที่ไม่ต้องใช้โค้ด อันทรงพลังสำหรับการสร้างแบ็กเอนด์ เว็บ และแอปพลิเคชันมือถือ AppMaster สร้างแอปพลิเคชันแบ็กเอนด์โดยใช้ Go เพื่อให้มั่นใจได้ถึงความสามารถในการปรับขนาดและประสิทธิภาพสูงที่จำเป็นสำหรับกรณีการใช้งานที่มีโหลดสูงและระดับองค์กร ในบทความนี้ เราจะกล่าวถึงเทคนิคการเพิ่มประสิทธิภาพที่จำเป็น โดยเริ่มจากการใช้ประโยชน์จากการสนับสนุนการทำงานพร้อมกันของ Go
ใช้ประโยชน์จากการทำงานพร้อมกันเพื่อประสิทธิภาพที่ดีขึ้น
การทำงานพร้อมกันทำให้สามารถทำงานหลายอย่างได้พร้อมกัน ทำให้ใช้ทรัพยากรระบบที่มีอยู่ให้เกิดประโยชน์สูงสุดและปรับปรุงประสิทธิภาพ Go ได้รับการออกแบบโดยคำนึงถึงการทำงานพร้อมกัน โดยให้ Goroutines และ Channels เป็นโครงสร้างภาษาในตัวเพื่อลดความซับซ้อนของการประมวลผลพร้อมกัน
โกรูทีน
Goroutines เป็นเธรดที่มีน้ำหนักเบาซึ่งจัดการโดยรันไทม์ของ Go การสร้าง Goroutine นั้นง่ายมาก เพียงใช้คีย์เวิร์ด `go` ก่อนเรียกใช้ฟังก์ชัน: ```go go funcName() ``` เมื่อ Goroutine เริ่มทำงาน มันจะแชร์พื้นที่ที่อยู่เดียวกันกับ Goroutine อื่นๆ สิ่งนี้ทำให้การสื่อสารระหว่าง Goroutines เป็นเรื่องง่าย อย่างไรก็ตาม คุณต้องดูแลการเข้าถึงหน่วยความจำที่ใช้ร่วมกันเพื่อป้องกันการแย่งชิงข้อมูล
ช่อง
ช่องเป็นรูปแบบหลักในการสื่อสารระหว่าง Goroutines in Go แชนเนลเป็นท่อร้อยสายซึ่งคุณสามารถส่งและรับค่าระหว่าง Goroutines ได้ หากต้องการสร้างแชนเนล ให้ใช้คีย์เวิร์ด `chan`: ``` go channelName := make(chan dataType) ``` การส่งและรับค่าผ่านแชนเนลทำได้โดยใช้โอเปอเรเตอร์ลูกศร (`<-`) นี่คือตัวอย่าง: ```go // การส่งค่าไปยังช่อง channelName <- valueToSend // การรับค่าจากช่องที่ได้รับValue := <-channelName ``` การใช้ช่องสัญญาณอย่างถูกต้องช่วยให้มั่นใจได้ถึงการสื่อสารที่ปลอดภัยระหว่าง Goroutines และขจัดสภาวะการแข่งขันที่อาจเกิดขึ้น .
การใช้รูปแบบการทำงานพร้อมกัน
การใช้รูปแบบการทำงานพร้อมกัน เช่น การขนาน ไปป์ไลน์ และพัดลมเข้า/พัดลมออก ช่วยให้นักพัฒนา Go สามารถ สร้างแอปพลิเคชันที่มีประสิทธิภาพ นี่คือคำอธิบายสั้น ๆ ของรูปแบบเหล่านี้:
- การทำงานแบบขนาน: แบ่งการคำนวณออกเป็นงานย่อยๆ และดำเนินการงานเหล่านี้พร้อมกันเพื่อใช้แกนประมวลผลหลายตัวและเพิ่มความเร็วในการคำนวณ
- ไปป์ไลน์: จัดระเบียบชุดของฟังก์ชันเป็นสเตจ โดยแต่ละสเตจจะประมวลผลข้อมูลและส่งต่อไปยังสเตจถัดไปผ่านแชนเนล สิ่งนี้สร้างไปป์ไลน์การประมวลผลที่ขั้นตอนต่าง ๆ ทำงานพร้อมกันเพื่อประมวลผลข้อมูลอย่างมีประสิทธิภาพ
- Fan-In/Fan-Out: กระจายงานข้าม Goroutines หลายตัว (fan-out) ซึ่งประมวลผลข้อมูลพร้อมกัน จากนั้น รวบรวมผลลัพธ์จาก Goroutines เหล่านี้เป็นช่องทางเดียว (fan-in) สำหรับการประมวลผลหรือการรวมเพิ่มเติม เมื่อใช้งานอย่างถูกต้อง รูปแบบเหล่านี้จะช่วยปรับปรุงประสิทธิภาพและความสามารถในการปรับขนาดของแอปพลิเคชัน Go ได้อย่างมาก
แอปพลิเคชัน Profileing Go เพื่อการปรับแต่งให้เหมาะสม
การทำโปรไฟล์คือกระบวนการวิเคราะห์โค้ดเพื่อระบุปัญหาคอขวดของประสิทธิภาพและความไร้ประสิทธิภาพในการใช้ทรัพยากร Go มีเครื่องมือในตัว เช่น แพ็คเกจ 'pprof' ที่ช่วยให้นักพัฒนาสร้างโปรไฟล์แอปพลิเคชันของตนและเข้าใจคุณลักษณะด้านประสิทธิภาพ การทำโปรไฟล์รหัส Go ของคุณทำให้คุณสามารถระบุโอกาสในการเพิ่มประสิทธิภาพและรับประกันการใช้ทรัพยากรอย่างมีประสิทธิภาพ
การทำโปรไฟล์ CPU
การทำโปรไฟล์ CPU จะวัดประสิทธิภาพของแอปพลิเคชัน Go ของคุณในแง่ของการใช้งาน CPU แพ็คเกจ "pprof" สามารถสร้างโปรไฟล์ CPU ที่แสดงตำแหน่งที่แอปพลิเคชันของคุณใช้เวลาดำเนินการส่วนใหญ่ หากต้องการเปิดใช้งานการทำโปรไฟล์ CPU ให้ใช้ข้อมูลโค้ดต่อไปนี้: ```go import "runtime/pprof" // ... func main() { // สร้างไฟล์เพื่อจัดเก็บโปรไฟล์ CPU f, err := os.Create( "cpu_profile.prof") ถ้า err != ไม่มี { log.Fatal(err) } เลื่อน f.Close() // เริ่มโปรไฟล์ CPU ถ้า err := pprof.StartCPUProfile(f); err != nil { log.Fatal(err) } defer pprof.StopCPUProfile() // เรียกใช้รหัสแอปพลิเคชันของคุณที่นี่ } ``` หลังจากเรียกใช้แอปพลิเคชันของคุณ คุณจะมีไฟล์ `cpu_profile.prof` ที่คุณสามารถวิเคราะห์ได้โดยใช้ เครื่องมือ `pprof` หรือแสดงภาพด้วยความช่วยเหลือของโปรแกรมสร้างโปรไฟล์ที่เข้ากันได้
การทำโปรไฟล์หน่วยความจำ
การทำโปรไฟล์หน่วยความจำเน้นที่การจัดสรรและการใช้งานหน่วยความจำของแอปพลิเคชัน Go ซึ่งช่วยให้คุณระบุการรั่วไหลของหน่วยความจำที่อาจเกิดขึ้น การจัดสรรที่มากเกินไป หรือพื้นที่ที่สามารถเพิ่มประสิทธิภาพหน่วยความจำได้ หากต้องการเปิดใช้งานการทำโปรไฟล์หน่วยความจำ ให้ใช้ข้อมูลโค้ดนี้: ```go import "runtime/pprof" // ... func main() { // เรียกใช้โค้ดแอปพลิเคชันของคุณที่นี่ // สร้างไฟล์เพื่อจัดเก็บโปรไฟล์หน่วยความจำ f, err := os.Create("mem_profile.prof") ถ้า err != nil { log.Fatal(err) } เลื่อน f.Close() // เขียนโปรไฟล์หน่วยความจำ runtime.GC() // ดำเนินการรวบรวมขยะเพื่อรับ สถิติหน่วยความจำที่ถูกต้อง ถ้าผิดพลาด := pprof.WriteHeapProfile(f); err != nil { log.Fatal(err) } } ``` เช่นเดียวกับโปรไฟล์ CPU คุณสามารถวิเคราะห์ไฟล์ `mem_profile.prof` โดยใช้เครื่องมือ `pprof` หรือแสดงภาพด้วยโปรแกรมสร้างโปรไฟล์ที่เข้ากันได้
ด้วยการใช้ประโยชน์จากความสามารถในการสร้างโปรไฟล์ของ Go คุณจะได้รับข้อมูลเชิงลึกเกี่ยวกับประสิทธิภาพของแอปพลิเคชันของคุณและระบุพื้นที่สำหรับการเพิ่มประสิทธิภาพ ซึ่งช่วยให้คุณสร้างแอปพลิเคชันที่มีประสิทธิภาพและประสิทธิภาพสูง ซึ่งปรับขนาดได้อย่างมีประสิทธิภาพและจัดการทรัพยากรได้อย่างเหมาะสมที่สุด
การจัดสรรหน่วยความจำและตัวชี้ใน Go
การจัดสรรหน่วยความจำให้เหมาะสมใน Go อาจส่งผลอย่างมากต่อประสิทธิภาพของแอปพลิเคชันของคุณ การจัดการหน่วยความจำที่มีประสิทธิภาพช่วยลดการใช้ทรัพยากร เร่งเวลาดำเนินการ และลดค่าใช้จ่ายในการรวบรวมขยะให้เหลือน้อยที่สุด ในส่วนนี้ เราจะพูดถึงกลยุทธ์ในการเพิ่มการใช้หน่วยความจำให้สูงสุดและการทำงานอย่างปลอดภัยด้วยพอยน์เตอร์
ใช้หน่วยความจำซ้ำเมื่อทำได้
วิธีหลักวิธีหนึ่งในการเพิ่มประสิทธิภาพการจัดสรรหน่วยความจำใน Go คือการนำวัตถุกลับมาใช้ใหม่ทุกครั้งที่ทำได้ แทนที่จะทิ้งวัตถุเหล่านั้นและจัดสรรวัตถุใหม่ Go ใช้การรวบรวมขยะเพื่อจัดการหน่วยความจำ ดังนั้นทุกครั้งที่คุณสร้างและละทิ้งวัตถุ ตัวรวบรวมขยะจะต้องล้างข้อมูลหลังจากแอปพลิเคชันของคุณ สิ่งนี้สามารถแนะนำค่าใช้จ่ายด้านประสิทธิภาพ โดยเฉพาะอย่างยิ่งสำหรับแอปพลิเคชันที่มีปริมาณงานสูง
พิจารณาใช้กลุ่มวัตถุ เช่น sync.Pool หรือการใช้งานแบบกำหนดเองของคุณ เพื่อนำหน่วยความจำกลับมาใช้อย่างมีประสิทธิภาพ กลุ่มวัตถุจัดเก็บและจัดการชุดของวัตถุที่แอปพลิเคชันสามารถนำมาใช้ซ้ำได้ ด้วยการใช้หน่วยความจำซ้ำผ่านกลุ่มวัตถุ คุณสามารถลดจำนวนหน่วยความจำโดยรวมและการจัดสรรคืนพื้นที่ได้ ลดผลกระทบของการรวบรวมขยะต่อประสิทธิภาพของแอปพลิเคชันของคุณ
หลีกเลี่ยงการจัดสรรที่ไม่จำเป็น
การหลีกเลี่ยงการจัดสรรที่ไม่จำเป็นช่วยลดแรงกดดันในการเก็บขยะ แทนที่จะสร้างวัตถุชั่วคราว ให้ใช้โครงสร้างข้อมูลหรือชิ้นส่วนที่มีอยู่ สามารถทำได้โดย:
- จัดสรรชิ้นส่วนล่วงหน้าด้วยขนาดที่ทราบโดยใช้
make([]T, size, capacity)
- ใช้ฟังก์ชัน
append
อย่างชาญฉลาดเพื่อหลีกเลี่ยงการสร้างส่วนตรงกลางระหว่างการต่อข้อมูล - หลีกเลี่ยงการผ่านโครงสร้างขนาดใหญ่ตามมูลค่า ให้ใช้ตัวชี้เพื่อส่งการอ้างอิงไปยังข้อมูลแทน
อีกแหล่งที่มาของการจัดสรรหน่วยความจำที่ไม่จำเป็นคือการใช้การปิด แม้ว่าการปิดจะสะดวก แต่ก็สามารถสร้างการจัดสรรเพิ่มเติมได้ เมื่อใดก็ตามที่เป็นไปได้ ให้ส่งพารามิเตอร์ของฟังก์ชันอย่างชัดเจน แทนที่จะจับค่าเหล่านั้นผ่านการปิด
ทำงานอย่างปลอดภัยด้วยพอยน์เตอร์
ตัวชี้เป็นโครงสร้างที่มีประสิทธิภาพใน Go ทำให้โค้ดของคุณสามารถอ้างอิงที่อยู่หน่วยความจำได้โดยตรง อย่างไรก็ตาม พาวเวอร์นี้อาจมาพร้อมกับข้อบกพร่องที่เกี่ยวข้องกับหน่วยความจำและปัญหาด้านประสิทธิภาพ ในการทำงานอย่างปลอดภัยกับพอยน์เตอร์ ให้ปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุดเหล่านี้:
- ใช้พอยน์เตอร์เท่าที่จำเป็นและเมื่อจำเป็นเท่านั้น การใช้งานที่มากเกินไปอาจทำให้การดำเนินการช้าลงและการใช้หน่วยความจำเพิ่มขึ้น
- รักษาขอบเขตของการใช้ตัวชี้ให้น้อยที่สุด ยิ่งขอบเขตมีขนาดใหญ่เท่าใด การติดตามการอ้างอิงและหลีกเลี่ยงการรั่วไหลของหน่วยความจำก็ยิ่งยากขึ้นเท่านั้น
- หลีก
unsafe.Pointer
เว้นแต่จำเป็นจริง ๆ เนื่องจากมันข้ามความปลอดภัยของ Go และอาจนำไปสู่ปัญหาที่ยากต่อการแก้ไข - ใช้แพ็กเก
sync/atomic
สำหรับการดำเนินการ atomic บนหน่วยความจำที่ใช้ร่วมกัน การทำงานของพอยน์เตอร์ปกติไม่ใช่ระดับปรมาณูและอาจนำไปสู่การแย่งชิงข้อมูลหากไม่ซิงโครไนซ์โดยใช้การล็อกหรือกลไกการซิงโครไนซ์อื่นๆ
การเปรียบเทียบแอปพลิเคชัน Go ของคุณ
การเปรียบเทียบคือกระบวนการวัดและประเมินประสิทธิภาพของแอปพลิเคชัน Go ของคุณภายใต้เงื่อนไขต่างๆ การทำความเข้าใจลักษณะการทำงานของแอปพลิเคชันของคุณภายใต้ปริมาณงานที่แตกต่างกันจะช่วยให้คุณระบุปัญหาคอขวด เพิ่มประสิทธิภาพการทำงาน และตรวจสอบว่าการอัปเดตไม่ทำให้ประสิทธิภาพการทำงานถดถอย
Go มีการสนับสนุนในตัวสำหรับการวัดประสิทธิภาพ โดยให้บริการผ่านแพ็คเกจ testing
ช่วยให้คุณสามารถเขียนการทดสอบเกณฑ์มาตรฐานที่วัดประสิทธิภาพรันไทม์ของโค้ดของคุณได้ คำสั่ง go test
ในตัวใช้เพื่อเรียกใช้เกณฑ์มาตรฐาน ซึ่งจะแสดงผลผลลัพธ์ในรูปแบบมาตรฐาน
การเขียนแบบทดสอบเกณฑ์มาตรฐาน
ฟังก์ชันเกณฑ์มาตรฐานมีการกำหนดคล้ายกับฟังก์ชันการทดสอบ แต่มีลายเซ็นที่แตกต่างกัน:
func BenchmarkMyFunction(b *testing.B) { // Benchmarking code goes here... }
ออบเจกต์ *testing.B
ที่ส่งผ่านไปยังฟังก์ชันมีคุณสมบัติและวิธีการที่เป็นประโยชน์หลายประการสำหรับการเปรียบเทียบ:
-
bN
: จำนวนการวนซ้ำของฟังก์ชันการเปรียบเทียบควรเรียกใช้ -
b.ReportAllocs()
: บันทึกจำนวนการจัดสรรหน่วยความจำระหว่างการวัดประสิทธิภาพ -
b.SetBytes(int64)
: ตั้งค่าจำนวนไบต์ที่ประมวลผลต่อการดำเนินการ ใช้ในการคำนวณปริมาณงาน
การทดสอบเกณฑ์มาตรฐานโดยทั่วไปอาจมีขั้นตอนต่อไปนี้:
- ตั้งค่าสภาพแวดล้อมที่จำเป็นและข้อมูลอินพุตสำหรับฟังก์ชันที่กำลังเปรียบเทียบ
- รีเซ็ตตัวจับเวลา (
b.ResetTimer()
) เพื่อลบเวลาการตั้งค่าออกจากการวัดเกณฑ์มาตรฐาน - วนซ้ำเกณฑ์มาตรฐานด้วยจำนวนการวนซ้ำที่กำหนด:
for i := 0; i < bN; i++
. - ดำเนินการฟังก์ชันที่กำลังเปรียบเทียบด้วยข้อมูลอินพุตที่เหมาะสม
เรียกใช้การทดสอบเกณฑ์มาตรฐาน
เรียกใช้การทดสอบเกณฑ์มาตรฐานของคุณด้วยคำสั่ง go test
รวมถึงแฟล็ก -bench
ตามด้วยนิพจน์ทั่วไปที่ตรงกับฟังก์ชันเกณฑ์มาตรฐานที่คุณต้องการเรียกใช้ ตัวอย่างเช่น:
go test -bench=.
คำสั่งนี้เรียกใช้ฟังก์ชันเกณฑ์มาตรฐานทั้งหมดในแพ็คเกจของคุณ หากต้องการเรียกใช้เกณฑ์มาตรฐานเฉพาะ ให้ระบุนิพจน์ทั่วไปที่ตรงกับชื่อ ผลลัพธ์การเปรียบเทียบจะแสดงในรูปแบบตาราง แสดงชื่อฟังก์ชัน จำนวนการวนซ้ำ เวลาต่อการดำเนินการ และการจัดสรรหน่วยความจำหากมีการบันทึกไว้
การวิเคราะห์ผลลัพธ์เกณฑ์มาตรฐาน
วิเคราะห์ผลการทดสอบเกณฑ์มาตรฐานของคุณเพื่อทำความเข้าใจลักษณะการทำงานของแอปพลิเคชันของคุณ และระบุจุดที่ต้องปรับปรุง เปรียบเทียบประสิทธิภาพของการใช้งานหรืออัลกอริทึมต่างๆ วัดผลกระทบของการเพิ่มประสิทธิภาพ และตรวจจับการถดถอยของประสิทธิภาพเมื่ออัปเดตโค้ด
เคล็ดลับเพิ่มเติมสำหรับการเพิ่มประสิทธิภาพ Go
นอกจากการเพิ่มประสิทธิภาพการจัดสรรหน่วยความจำและการเปรียบเทียบแอปพลิเคชันของคุณแล้ว ต่อไปนี้เป็นเคล็ดลับอื่นๆ สำหรับการปรับปรุงประสิทธิภาพของโปรแกรม Go ของคุณ:
- อัปเดตเวอร์ชัน Go ของคุณ : ใช้ Go เวอร์ชันล่าสุดเสมอ เนื่องจากมักจะมีการปรับปรุงประสิทธิภาพและการเพิ่มประสิทธิภาพ
- ฟังก์ชันแบบอินไลน์หากทำได้ : ฟังก์ชันแบบอินไลน์สามารถช่วยลดค่าใช้จ่ายในการเรียกใช้ฟังก์ชัน ซึ่งช่วยปรับปรุงประสิทธิภาพ ใช้
go build -gcflags '-l=4'
เพื่อควบคุมความก้าวร้าวในการอินไลน์ (ค่าที่สูงขึ้นจะเพิ่มการอินไลน์) - ใช้แชนเนลแบบบัฟเฟอร์ : เมื่อทำงานกับการทำงานพร้อมกันและใช้แชนเนลสำหรับการสื่อสาร ให้ใช้แชนเนลแบบบัฟเฟอร์เพื่อป้องกันการปิดกั้นและปรับปรุงทรูพุต
- เลือกโครงสร้างข้อมูลที่เหมาะสม : เลือกโครงสร้างข้อมูลที่เหมาะสมที่สุดสำหรับความต้องการของแอปพลิเคชันของคุณ ซึ่งอาจรวมถึงการใช้สไลซ์แทนอาร์เรย์เมื่อเป็นไปได้ หรือใช้แผนที่ในตัวและชุดสำหรับการค้นหาและการจัดการที่มีประสิทธิภาพ
- ปรับโค้ดของคุณให้เหมาะสม - ทีละเล็กทีละน้อย : มุ่งเน้นที่การปรับให้เหมาะสมทีละส่วน แทนที่จะพยายามจัดการทุกอย่างพร้อมกัน เริ่มต้นด้วยการจัดการกับความไร้ประสิทธิภาพของอัลกอริทึม จากนั้นจึงย้ายไปที่การจัดการหน่วยความจำและการเพิ่มประสิทธิภาพอื่นๆ
การใช้เทคนิคการเพิ่มประสิทธิภาพเหล่านี้ในแอปพลิเคชัน Go ของคุณอาจมีผลกระทบอย่างมากต่อความสามารถในการปรับขนาด การใช้ทรัพยากร และประสิทธิภาพโดยรวม ด้วยการใช้ประโยชน์จากพลังของเครื่องมือในตัวของ Go และความรู้เชิงลึกที่แบ่งปันในบทความนี้ คุณจะมีความพร้อมในการพัฒนาแอปพลิเคชันที่มีประสิทธิภาพสูงซึ่งสามารถรองรับปริมาณงานที่หลากหลายได้
ต้องการพัฒนาแอปพลิเคชันแบ็กเอนด์ที่ปรับขนาดได้และมีประสิทธิภาพด้วย Go หรือไม่ พิจารณา AppMaster ซึ่งเป็น แพลตฟอร์มแบบไม่ใช้โค้ด อันทรงพลังที่สร้างแอปพลิเคชันส่วนหลังโดยใช้ Go (Golang) เพื่อประสิทธิภาพสูงและความสามารถในการปรับขนาดที่น่าทึ่ง ทำให้เป็นตัวเลือกที่เหมาะสำหรับกรณีการใช้งานที่มีโหลดสูงและระดับองค์กร เรียนรู้เพิ่มเติมเกี่ยวกับ AppMaster และวิธีการที่จะปฏิวัติกระบวนการพัฒนาของคุณ