นักพัฒนาได้สร้างเครื่องมือดีบักที่นวัตกรรมใหม่ที่ใช้ eBPF ( Extended Berkeley Packet Filter ) เพื่อติดตามการจัดสรรหน่วยความจำในโปรแกรม Go ตามประเภทข้อมูล ซึ่งแก้ไขช่องว่างที่สำคัญในเครื่องมือ profiling ที่มีอยู่ของ Go ที่สามารถแสดงตำแหน่งที่เกิดการจัดสรรได้ แต่ไม่สามารถบอกได้ว่าข้อมูลประเภทใดถูกจัดสรร
ปัญหาของเครื่องมือ Profiling ในตัวของ Go
เครื่องมือ profiling มาตรฐานของ Go ให้ข้อมูลเชิงลึกที่มีค่าเกี่ยวกับตำแหน่งการจัดสรรหน่วยความจำ แต่ยังไม่เพียงพอเมื่อนักพัฒนาต้องการเข้าใจว่าประเภทข้อมูลใดใช้หน่วยความจำมากที่สุด ข้อจำกัดนี้กลายเป็นปัญหาโดยเฉพาะเมื่อประเภทข้อมูลเฉพาะทำให้เกิดการจัดสรรหนักในหลายตำแหน่งของโค้ด ทำให้ยากต่อการระบุสาเหตุหลักของความดันหน่วยความจำ
ชุมชนต่างประสบปัญหาการจัดสรร string ใน Go มาอย่างยาวนาน นักพัฒนาคนหนึ่งสังเกตว่าการหลีกเลี่ยงการจัดสรร heap สำหรับ string นั้นยากมาก โดยเฉพาะเมื่อใช้ฟังก์ชันการจัดรูปแบบ เนื่องจาก string ที่ส่งไปยังฟังก์ชัน fmt มักหลุดไปยัง heap เนื่องจากข้อจำกัดในการจัดการ interface{}
การใช้งานทางเทคนิคโดยใช้ eBPF
วิธีแก้ปัญหาเกี่ยวข้องกับการแนบ eBPF uprobe เข้ากับฟังก์ชัน mallocgc
ภายในของ Go ซึ่งจัดการการจัดสรร heap ทั้งหมด โดยการสกัดกั้นการเรียกฟังก์ชันนี้ เครื่องมือจะจับข้อมูลทั้งขนาดการจัดสรรและข้อมูลประเภทจาก CPU registers ความท้าทายอยู่ที่การถอดรหัสการแสดงประเภทภายในของ Go ซึ่งใช้ integer offsets แทนชื่อ string โดยตรง
การใช้งานต้องการการแยกวิเคราะห์ส่วน ELF executable และสร้างตรรกะการแก้ไขประเภทภายในของ Go ขึ้นใหม่เพื่อแปลง offsets เหล่านี้กลับเป็นชื่อประเภทที่อ่านได้ กระบวนการนี้เกี่ยวข้องกับการนำทางผ่าน linked lists ของข้อมูลโมดูลที่เก็บไว้ในส่วนคงที่ของ executable
การค้นพบแหล่งการจัดสรรที่ซ่อนอยู่
เครื่องมือเผยให้เห็นว่าการจัดสรรจำนวนมากเกิดขึ้นกับ null type pointers เมื่อข้อมูลที่จัดสรรไม่มี pointers เพื่อจับการจัดสรรที่มองไม่เห็นเหล่านี้ probes เพิ่มเติมถูกแนบเข้ากับฟังก์ชัน runtime เช่น makechan
, makeslicecopy
และ growslice
พร้อมกับตัวระบุประเภทที่กำหนดเองเพื่อติดตามแหล่งการจัดสรรที่แตกต่างกัน
ข้อมูลเชิงลึกด้านประสิทธิภาพในโลกจริง
การสืบสวนยืนยันข้อสงสัยเกี่ยวกับรูปแบบโค้ดที่มีปัญหา โดยเฉพาะฟังก์ชันที่ส่งคืน pointers ไปยัง strings แทนที่จะส่งคืน strings โดยตรง รูปแบบต่อต้านทั่วไปปรากฏขึ้นที่ methods ส่งคืน *string
เพื่อจัดการกรณี nil ทำให้เกิดการจัดสรร heap ที่ไม่จำเป็นและ pointer indirection เพิ่มเติม
ปัญหาใหญ่ที่สุดคือ string ใดๆ ที่คุณส่งเป็นอาร์กิวเมนต์ไปยังฟังก์ชัน fmt จะถูกย้ายไปยัง heap เพราะ interface{} ถูกนับว่าหลุดจาก stack เสมอ
การปรับปรุงในอนาคตและทางเลือกอื่น
แม้ว่าแนวทาง eBPF นี้จะให้ข้อมูลเชิงลึกทันที แต่ชุมชน Go ตระหนักถึงความจำเป็นในการมีวิธีแก้ปัญหาในตัวที่ดีกว่า การอภิปรายกำลังดำเนินอยู่เกี่ยวกับการเพิ่มข้อมูลประเภทลงใน allocation profiles ผ่าน pprof labels แม้ว่าจะยังมีความท้าทายเกี่ยวกับการใช้งานโดยไม่มี memory leaks ใน long-running profiles
การเปิดตัว Go 1.25 ที่กำลังจะมาถึงรวมการปรับปรุงสำหรับการจัดการ string ที่ควรลดความดันการจัดสรรบางส่วน แก้ไขปัญหาที่มีมายาวนานกับ escape analysis สำหรับพารามิเตอร์ interface{}
เครื่องมือนี้แสดงให้เห็นทั้งพลังของ eBPF สำหรับการตรวจสอบ runtime และวิวัฒนาการที่กำลังดำเนินอยู่ของระบบนิเวศเครื่องมือประสิทธิภาพของ Go แม้ว่าจะเปราะบางตามที่ยอมรับ แต่ก็ให้ข้อมูลเชิงลึกที่มีค่าซึ่งสามารถแนะนำความพยายามในการปรับปรุงประสิทธิภาพในแอปพลิเคชันที่ต้องการหน่วยความจำ
อ้างอิง: Go allocation probe